Using Genie with Docker
Genie comes with extended support for containerizing apps using Docker. The functionality is provided by the official GenieDeployDocker plugin.
Setting up GenieDeployDocker
In order to use the Docker integration features, first we need to add the GenieDeployDocker plugin for Genie.
pkg> add GenieDeployDockerGenerating the Genie-optimised Dockerfile
You can bootstrap the Docker setup by invoking the GenieDeployDocker.dockerfile() function. This will generate a custom Dockerfile optimized for Genie web apps containerization. The file will be generated in the current work dir (or where instructed by the optional argument path – see the help for the dockerfile() function).
Once generated, you can edit it and customize it as needed - Genie will not overwrite the file, thus preserving any changes (unless you call the dockerfile function again, passing the force=true argument).
The behavior of dockerfile() can be controlled by passing any of the multiple optional arguments supported.
Building the Docker container
Once we have our Dockerfile ready, we can invoke GenieDeployDocker.build() to set up the Docker container. You can pass any of the supported optional arguments to configure settings such as the container's name (by default "genie"), the path (defaults to current work dir), and others (see the output of help?> GenieDeployDocker.dockerfile for all the available options).
Running the Genie app within the Docker container
When the image is ready, we can run it with GenieDeployDocker.run(). We can configure any of the optional arguments in order to control how the app is run. Check the inline help for the function for more details.
Examples
First let's create a Genie app:
julia> using Genie
julia> Genie.Generator.newapp("DockerTest")
[ Info: Done! New app created at /your/app/path/DockerTest
# output truncatedWhen it's ready, let's add the Dockerfile:
julia> using GenieDeployDocker
julia> GenieDeployDocker.dockerfile()
Docker file successfully written at /your/app/path/DockerTest/DockerfileNow, to build our container:
julia> GenieDeployDocker.build()
# output truncated
Successfully tagged genie:latest
Docker container successfully builtAnd finally, we can now run our app within the Docker container:
julia> GenieDeployDocker.run()
Starting docker container with `docker run -it --rm -p 80:8000 --name genieapp genie bin/server`
# output truncatedWe should then see the familiar Genie loading screen, indicating the app's loading progress and notifying us once the app is running.
Our application starts inside the Docker container, binding port 8000 within the container (where the Genie app is running) to the port 80 of the host. So we are now able to access our app at http://localhost. If you navigate to http://localhost w ith your favorite browser you'll see Genie's welcome page. Notice that we don't access on port 8000 - this page is served from the Docker container on the default port 80.
Inspecting the containers
We can get a list of available container by using GenieDeployDocker.list(). This will show only the currently running containers by default, but we can pass the all=true argument to also include containers that are offline.
julia> GenieDeployDocker.list()
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c87bfd8322cc genie "bin/server" 6 minutes ago Up 6 minutes 80/tcp, 0.0.0.0:80->8000/tcp genieapp
Process(`docker ps`, ProcessExited(0))Stopping running containers
The running containers can be stopped by using the GenieDeployDocker.stop() function, passing the name of the container.
julia> GenieDeployDocker.stop("genieapp")Using Docker during development
If we want to use Docker to serve the app during development, we need to mount our app from host (your computer) into the container – so that we can keep editing our files locally, but see the changes reflected in the Docker container. In order to do this we need to pass the mountapp = true argument to GenieDeployDocker.run(), like this:
julia> GenieDeployDocker.run(mountapp = true)
Starting docker container with `docker run -it --rm -p 80:8000 --name genieapp -v /Users/adrian/DockerTest:/home/genie/app genie bin/server`When the app finishes starting, we can edit the files on the host using our favorite IDE, and see the changes reflected in the Docker container.
Creating an optimized Genie sysimage with PackageCompiler.jl
If we are using Docker containers to deploy Genie apps in production, you can greatly improve the performance of the app by preparing a precompiled sysimage for Julia. We can include this workflow as part of the Docker build step as follows.
Edit the Dockerfile
We'll start by making a few changes to our Dockerfile, as follows:
1/ Under the line WORKDIR /home/genie/app add
# C compiler for PackageCompiler
RUN apt-get update && apt-get install -y g++2/ Under the line starting with RUN julia -e add
# Compile app
RUN julia --project compiled/make.jlYou may also want to replace the line saying
ENV GENIE_ENV "dev"with
ENV GENIE_ENV "prod"to configure the application to run in production (first test locally to make sure that everything is properly configured to run the app in production environment).
Add PackageCompiler.jl
We also need to add PackageCompiler as a dependency of our app:
pkg> add PackageCompilerAdd the needed files
Create a new folder to host our files:
julia> mkdir("compiled")Now create the following files:
julia> touch("compiled/make.jl")
julia> touch("compiled/packages.jl")Edit the files
Now to put the content into each of the files.
Preparing the packages.jl file
Here we simply put an array of package that our app uses and that we want to precompile, ex:
# packages.jl
const PACKAGES = [
"Dates",
"Genie",
"Inflector",
"Logging"
]Preparing the make.jl file
Now edit the make.jl file as follows:
# make.jl
using PackageCompiler
include("packages.jl")
PackageCompiler.create_sysimage(
PACKAGES,
sysimage_path = "compiled/sysimg.so",
cpu_target = PackageCompiler.default_app_cpu_target()
)Using the precompiled image
The result of these changes is that PackageCompiler will create a new Julia sysimage that will be stored inside the compiled/sysimg.so file. The last step is to instruct our bin/server script to use the image.
Edit the bin/server file and make it look like this:
julia --color=yes --depwarn=no --project=@. --sysimage=compiled/sysimg.so -q -i -- $(dirname $0)/../bootstrap.jl -s=true "$@"With this change we're passing the additional --sysimage flag, indicating our new Julia sys image.