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 GenieDeployDocker
Generating 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 truncated
When it's ready, let's add the Dockerfile
:
julia> using GenieDeployDocker
julia> GenieDeployDocker.dockerfile()
Docker file successfully written at /your/app/path/DockerTest/Dockerfile
Now, to build our container:
julia> GenieDeployDocker.build()
# output truncated
Successfully tagged genie:latest
Docker container successfully built
And 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 truncated
We 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.jl
You 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 PackageCompiler
Add 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.
Working with Genie projects
Working with Genie in an interactive environment can be useful – but usually we want to persist the application and reuse it between sessions. One way to achieve this is to save it as an IJulia notebook and rerun the cells.
External packages
Using third party packages in a Genie app.