
Building and running the release image
Now that we have completed the configuration of the release stage of the Dockerfile, it's time to build our new released image and verify we can actually run our application successfully.
To build the image, we can use the docker build command, and because the release stage is the last stage of the Dockerfile, you don't need to target a specific stage, as we did previously for the test stage:
> docker build -t todobackend-release .
Sending build context to Docker daemon 312.8kB
Step 1/22 : FROM alpine AS test
---> 3fd9065eaf02
...
...
Step 13/22 : FROM alpine
---> 3fd9065eaf02
Step 14/22 : LABEL application=todobackend
---> Using cache
---> afdd1dee07d7
Step 15/22 : RUN apk add --no-cache python3 mariadb-client bash
---> Using cache
---> dfe0b6487459
Step 16/22 : RUN addgroup -g 1000 app && adduser -u 1000 -G app -D app
---> Running in d75df9cadb1c
Removing intermediate container d75df9cadb1c
---> ac26efcbfea0
Step 17/22 : COPY --from=test --chown=app:app /build /build
---> 1f177a92e2c9
Step 18/22 : COPY --from=test --chown=app:app /app /app
---> ba8998a31f1d
Step 19/22 : RUN pip3 install -r /build/requirements.txt -f /build --no-index --no-cache-dir
---> Running in afc44357fae2
Looking in links: /build
Collecting Django==2.0 (from -r /build/requirements.txt (line 1))
Collecting django-cors-headers==2.1.0 (from -r /build/requirements.txt (line 2))
Collecting djangorestframework==3.7.3 (from -r /build/requirements.txt (line 3))
Collecting mysql-connector-python==8.0.11 (from -r /build/requirements.txt (line 4))
Collecting pytz==2017.3 (from -r /build/requirements.txt (line 5))
Collecting uwsgi (from -r /build/requirements.txt (line 6))
Collecting protobuf>=3.0.0 (from mysql-connector-python==8.0.11->-r /build/requirements.txt (line 4))
Requirement already satisfied: setuptools in /usr/lib/python3.6/site-packages (from protobuf>=3.0.0->mysql-connector-python==8.0.11->-r /build/requirements.txt (line 4)) (28.8.0)
Collecting six>=1.9 (from protobuf>=3.0.0->mysql-connector-python==8.0.11->-r /build/requirements.txt (line 4))
Installing collected packages: pytz, Django, django-cors-headers, djangorestframework, six, protobuf, mysql-connector-python, uwsgi
Successfully installed Django-2.0 django-cors-headers-2.1.0 djangorestframework-3.7.3 mysql-connector-python-8.0.11 protobuf-3.6.0 pytz-2017.3 six-1.11.0 uwsgi-2.0.17
Removing intermediate container afc44357fae2
---> ab2bcf89fe13
Step 20/22 : RUN rm -rf /build
---> Running in 8b8006ea8636
Removing intermediate container 8b8006ea8636
---> ae7f157d29d1
Step 21/22 : WORKDIR /app
Removing intermediate container fbd49835ca49
---> 55856af393f0
Step 22/22 : USER app
---> Running in d57b2cb9bb69
Removing intermediate container d57b2cb9bb69
---> 8170e923b09a
Successfully built 8170e923b09a
Successfully tagged todobackend-release:latest
At this point, we can run the Django application that is located in the release image, but you might be wondering exactly how that works. When we ran the python3 manage.py runserver command earlier, it spun up a local development web server which is not recommended for production-user cases, so we require an alternative web server to run our application in production.
You may have noticed earlier in the requirements.txt file a package called uwsgi—this is a very popular web server that can be used in production, and, conveniently for our use case, can be installed via PIP. This means that uwsgi is already available as a web server in our release container and can be used to serve the sample application:
> docker run -it --rm -p 8000:8000 todobackend-release uwsgi \
--http=0.0.0.0:8000 --module=todobackend.wsgi --master
*** Starting uWSGI 2.0.17 (64bit) on [Tue Jul 3 11:44:44 2018] ***
compiled with version: 6.4.0 on 02 July 2018 14:34:31
os: Linux-4.9.93-linuxkit-aufs #1 SMP Wed Jun 6 16:55:56 UTC 2018
nodename: 5be4dd1ddab0
machine: x86_64
clock source: unix
detected number of CPU cores: 1
current working directory: /app
detected binary path: /usr/bin/uwsgi
!!! no internal routing support, rebuild with pcre support !!!
your memory page size is 4096 bytes
detected max file descriptor number: 1048576
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uWSGI http bound on 0.0.0.0:8000 fd 4
uwsgi socket 0 bound to TCP address 127.0.0.1:35765 (port auto-assigned) fd 3
Python version: 3.6.3 (default, Nov 21 2017, 14:55:19) [GCC 6.4.0]
*** Python threads support is disabled. You can enable it with --enable-threads ***
Python main interpreter initialized at 0x55e9f66ebc80
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 145840 bytes (142 KB) for 1 cores
*** Operational MODE: single process ***
WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x55e9f66ebc80 pid: 1 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 1)
spawned uWSGI worker 1 (pid: 7, cores: 1)
spawned uWSGI http 1 (pid: 8)
We use the -p flag to map port 8000 on the container to port 8000 on your host, and execute the uwsgi command passing in various configuration flags that run the application on port 8000 and specify the todobackend.wsgi module as the application served by uwsgi.
At this point, you can browse to http://localhost:8000 and although the application does return a response, you will find that the web server and application are missing a bunch of static content:

The problem here is that Django automatically generates static content when you run the Django development web server, however, when you run the application in production along with an external web server, you are responsible for generating static content yourself. We will learn how to do this later on in this chapter, however for now, you can verify the API works by using curl:
> curl -s localhost:8000/todos | jq
[
{
"url": "http://localhost:8000/todos/1",
"title": "Walk the dog",
"completed": false,
"order": 1
},
{
"url": "http://localhost:8000/todos/2",
"title": "Wash the car",
"completed": true,
"order": 2
}
]
One thing to note here is that the todobackend data has the same data that we loaded back in Chapter 1, despite us having built a Docker image from scratch. The problem here is that the SQLite database that was created back in Chapter 1 resides in the src folder, in a file called db.sqlite3. Clearly, we don't want to copy this file into our Docker image during the build process, and one way to achieve this is to create a .dockerignore file at the root of the repository:
# Ignore SQLite database files
**/*.sqlite3
# Ignore test output and private code coverage files
**/*.xml
**/.coverage
# Ignore compiled Python source files
**/*.pyc
**/pycache
# Ignore macOS directory metadata files
**/.DS_Store
The .dockerignore file works similarly to .gitignore in a Git repository, and is used to exclude files from the Docker build context. Because the db.sqlite3 file is located in a subfolder, we use a wildcard globing pattern of ** (note this is different from .gitignore behavior, which globs by default), which means we recursively exclude any file matching the wildcard pattern. We also exclude any test output files that have a .xml extension, code coverage files, the __pycache__ folder, and any compiled Python files with .pyc extensions, which are intended to be generated on the fly at runtime.
If you now rebuild the Docker image and start-up the the uwsgi web server locally on port 8000, when you browse to the application (http://localhost:8000), you will get a different error:

The problem now is that no database exists for the todobackend application, so the application is failing as it cannot locate the table that stores Todo items. To resolve this problem, we are now at the point where we need to integrate with an external database engine, meaning we need a solution to work with multiple containers locally.