Production deployment using Docker

Get started

Check out the example on your development machine.

In this example you will create a docker image for your example app with all its runtime dependencies installed along with the required config using docker-compose on your local machine to simulate a production environment.

The web server (Nginx) and database (PostgreSQL) are run in separate containers.

The example assumes you need a PostgreSQL database, however it can be changed to be MySQL fairly easily.

Follow the instructions for your chosen platform to install docker, and also docker-compose.

Create docker images

In the checked out example directory, build the required images.

Note

docker-compose will reference the provided docker-compose.yml file.

docker-compose build

Verify that the required images built successfully:

docker images | grep hellodockernginx

Expect output similar to:

hellodockernginx_web       latest              e41c79d7ca1a        8 seconds ago       153MB
hellodockernginx           latest              a351ff871b20        15 seconds ago      270MB

Understanding docker-compose.yaml file

The docker-compose.yaml file in this example configures 3 services:

database

This runs a PostgreSQL server, in a container named postgresql, with super user and password as specified in the given environment variables.

app

The hellodockernginx image runs your app in a uwsgi server. Your app’s config points to the database service using the default ports. See prod/etc/reahl.config.py:


# In production this has to be set, to the name of the egg of your application:
reahlsystem.root_egg = 'hellodockernginx'   

# If using PostgreSQL:
reahlsystem.connection_uri = 'postgresql://hellodockernginx:hellodockernginx@database/hellodockernginx'

# If using MySQL:
#reahlsystem.connection_uri = 'mysql://hellodockernginx:hellodockernginx@database/hellodockernginx'

reahlsystem.debug = False

Since this example uses services defined for docker-compose, this configuration can hard-code the service name database.

The uwsi config ensures the hellodockernginxwsgi module is run by uwsgi:

[uwsgi]
socket = :8080
module = hellodockernginxwsgi:application
venv = /app/venv
master = 1
processes = 4
plugin = python3
uid = www-data
gid = www-data

The hellodockernginxwsgi module is part of your application and hard-codes where the Reahl configuration is read from:


from reahl.web.fw import ReahlWSGIApplication
application = ReahlWSGIApplication.from_directory('/etc/app-reahl', start_on_first_request=True)

Locations in the built image to take note of:

  • App is installed in a venv

    /app/venv

  • App’s Reahl config directory

    /etc/app-reahl

  • App static file location

    /app/www

  • wsgi config

    /etc/app-wsgi.ini

This image is built using prod/Dockerfile which works in two stages base and build:
  • base In this stage, development dependencies are installed, and a wheel is built for your app.

  • build In this stage, a venv is created, and your built wheel is installed along with its static files.

web

The web service runs nginx in a container named hellodockernginx_web. Nginx is configured in prod/nginx/app.conf to reverse-proxy to your app using the uwsgi_pass directive. Note that since this is using services defined for docker-compose, this configuration can hard-code the service name app:


server {
       listen 80;
       server_name _;

       location / {
          include uwsgi_params;
          uwsgi_param HTTPS off;
          uwsgi_pass app:8080;
          uwsgi_ignore_headers   Set-Cookie;
       }
}

server {
       listen 443 ssl;
       server_name _;

       #ssl_certificate /etc/ssl/certs/app.pem;
       #ssl_certificate_key /etc/ssl/private/app.key;
       ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
       ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

       ssl_session_timeout 5m;
       ssl_protocols SSLv3 TLSv1.1 TLSv1.2;
       ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
       ssl_prefer_server_ciphers on;

       location / {
          include uwsgi_params;
          uwsgi_param HTTPS on;
          uwsgi_pass app:8080;
          uwsgi_ignore_headers   Set-Cookie;
       }

}

In order to be able to copy this config into the built image, this service also builds its container from prod/nginx/Dockerfile:

FROM nginx:1.19

RUN apt-get update && \
    apt-get install ssl-cert && \
    rm -rf /var/cache/apt/*
COPY prod/nginx/app.conf /etc/nginx/conf.d/default.conf

Note

This configuration uses the insecure snakeoil certificates shipped in the ssl-cert package. You will install your own, mounting the actual certificates via a volume external to the image itself.

Locations in the built image to take note of:

  • nginx config

    /etc/nginx/conf.d/default.conf

Test the image locally using docker-compose

Spin up containers

Before deploying the images in your production environment, you can test them locally. Spin up containers for the built images and connect to your app.

docker-compose up -d

Expect:

Creating network "hellodockernginx_default" with the default driver
Creating hellodockernginx_database_1 ... done
Creating hellodockernginx_app_1      ... done
Creating hellodockernginx_web_1      ... done

List the running containers:

docker container list

Expect output:

CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                                         NAMES
ca0dd108aa59        hellodockernginx_web   "/docker-entrypoint.…"   2 hours ago         Up 2 hours          0.0.0.0:8080->80/tcp, 0.0.0.0:8443->443/tcp   hellodockernginx_web_1
1e91b70b24c7        hellodockernginx       "uwsgi --ini /etc/ap…"   2 hours ago         Up 2 hours          8080/tcp                                      hellodockernginx_app_1
26c5e89f5fee        postgres:12.3          "docker-entrypoint.s…"   2 hours ago         Up 2 hours          5432/tcp                                      hellodockernginx_database_1

Create and initialise the database

Prepare the database for your app by executing:

# If using PostgreSQL:
docker-compose exec -T -e PGPASSWORD=reahl app /app/venv/bin/reahl createdbuser -U developer /etc/app-reahl
docker-compose exec -T -e PGPASSWORD=reahl app /app/venv/bin/reahl createdb -U developer /etc/app-reahl
docker-compose exec -T app /app/venv/bin/reahl createdbtables  /etc/app-reahl

# If using MySQL:
#docker-compose exec -T -e MYSQL_PWD=reahl app /app/venv/bin/reahl createdbuser -U root /etc/app-reahl
#docker-compose exec -T -e MYSQL_PWD=reahl app /app/venv/bin/reahl createdb -U root /etc/app-reahl
#docker-compose exec -T app /app/venv/bin/reahl createdbtables  /etc/app-reahl

Check the database:

docker-compose exec -T app /app/venv/bin/reahl sizedb /etc/app-reahl

Expect output like:

Database size: 8177 kB

Inspect running app container

To inspect the app container, step into it with:

docker exec -ti hellodockernginx_app_1 bash -l

View the logs for the app container:

docker logs hellodockernginx_app_1

Test your app being served by nginx

Open a browser tab to localhost:8080 and expect to see Hello World!

Or test it from a command line:

python3 -c "from urllib.request import urlopen; import re; print(re.search(r'<p>.*?</p>', urlopen('http://localhost:8080').read().decode('utf-8')).group(0))"

Similarly, expect:

`<p>Hello World!<p>`

Changes for a MySQL database

Modify these files that have been annotated with references to MySQL:

  • pyproject.toml

    Replace the dependency on “reahl-postgresqlsupport” with “reahl-mysqlsupport”

  • prod/etc/reahl.config.d

    Modify the config to contain the MySQL required settings

  • prod/Dockerfile

    Change the ENV variables to cater for MySQL dependencies

  • scrips/setup_database.sh

    Use the commands to connect to MySQL database container

  • docker-compose.yml

    Replace the Postgres database section with the MySQL section

Note

run `docker-compose down` to stop and discard running containers.

Build and run the docker images again by following similar instructions given above.