Docker Config

Part 1, Chapter 6


Let's containerize the Flask app.


Start by ensuring that you have Docker, Docker Compose, and Docker Machine installed:

$ docker -v
Docker version 18.09.2, build 6247962

$ docker-compose -v
docker-compose version 1.23.2, build 1110ad01

$ docker-machine -v
docker-machine version 0.16.1, build cce350d7

Add a Dockerfile to the "users" directory, making sure to review the code comments:

# base image
FROM python:3.7.2-alpine

# set working directory
WORKDIR /usr/src/app

# add and install requirements
COPY ./requirements.txt /usr/src/app/requirements.txt
RUN pip install -r requirements.txt

# add app
COPY . /usr/src/app

# run server
CMD python manage.py run -h 0.0.0.0

Here, we used an Alpine-based, Python image to keep our final image slim. Alpine Linux is a lightweight Linux distro. It's a good practice to use Alpine-based images, whenever possible, as your base images in your Dockerfiles.

Benefits of using Alpine:

  1. Decreased hosting costs since less disk space is used
  2. Quicker build, download, and run times
  3. More secure (since there are less packages and libraries)
  4. Faster deployments

Depending on your environment, you may need to add RUN mkdir -p /usr/src/app just before you set the working directory:

# set working directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

Add .dockerignore to the "users" directory as well:

env
.dockerignore
Dockerfile
Dockerfile-prod

Like the .gitignore file, the .dockerignore file lets you exclude certain files and folders from being copied over to the image.

Review Docker for Python Developers for more on structuring Dockerfiles as well as some best practices for configuring Docker for Python-based development.

Then add a docker-compose.yml file to the project root:

version: '3.7'

services:

  users:
    build:
      context: ./services/users
      dockerfile: Dockerfile
    volumes:
      - './services/users:/usr/src/app'
    ports:
      - 5001:5000
    environment:
      - FLASK_APP=project/__init__.py
      - FLASK_ENV=development

This config will create a service called users, from the Dockerfile.

Directories are relative to the docker-compose.yml file.

The volume is used to mount the code into the container. This is a must for a development environment in order to update the container whenever a change to the source code is made. Without this, you would have to re-build the image each time you make a change to the code.

Take note of the Docker compose file version usedr—3.7. Keep in mind that this does not relate directly to the version of Docker Compose installed; it simply specifies the file format that you want to use.

Build the image from the project root:

$ docker-compose build

This will take a few minutes the first time. Subsequent builds will be much faster since Docker caches the results. Once the build is done, fire up the container:

$ docker-compose up -d

The -d flag is used to run containers in the background.

Navigate to http://localhost:5001/users/ping. Make sure you see the same JSON response as before:

{
  "message": "pong!",
  "status": "success"
}

Windows Users: Having problems getting the volume to work properly? Review the following resources:

  1. Docker on Windows — Mounting Host Directories
  2. Configuring Docker for Windows Shared Drives

You also may need to add COMPOSE_CONVERT_WINDOWS_PATHS=1 to the environment portion of your Docker Compose file. Review Declare default environment variables in file for more info.

Next, add an environment variable to the docker-compose.yml file to load the app config for the dev environment:

version: '3.7'

services:

  users:
    build:
      context: ./services/users
      dockerfile: Dockerfile
    volumes:
      - './services/users:/usr/src/app'
    ports:
      - 5001:5000
    environment:
      - FLASK_APP=project/__init__.py
      - FLASK_ENV=development
      - APP_SETTINGS=project.config.DevelopmentConfig  # new

Then update project/__init__.py, to pull in the environment variables:

# services/users/project/__init__.py


import os  # new
from flask import Flask, jsonify
from flask_restful import Resource, Api


# instantiate the app
app = Flask(__name__)

api = Api(app)

# set config
app_settings = os.getenv('APP_SETTINGS')  # new
app.config.from_object(app_settings)      # new


class UsersPing(Resource):
    def get(self):
        return {
        'status': 'success',
        'message': 'pong!'
    }


api.add_resource(UsersPing, '/users/ping')

Update the container:

$ docker-compose up -d --build

Want to test, to ensure the proper config was loaded? Add a print statement to __init__.py, right before the route handler, to view the app config to ensure that it is working:

import sys
print(app.config, file=sys.stderr)

Then open the logs:

$ docker-compose logs

You should see something like:

<Config {
  'ENV': 'development', 'DEBUG': True, 'TESTING': False,
  'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None,
  'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31),
  'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/',
  'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None,
  'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True,
  'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None,
  'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None,
  'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200),
  'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False,
  'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http',
  'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR':
  False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None,
  'MAX_COOKIE_SIZE': 4093}
>

Make sure to remove the print statement before moving on.




Mark as Completed