Docker Config
Part 1, Chapter 4
Let's containerize the Flask app.
Start by ensuring that you have Docker and Docker Compose:
$ docker -v
Docker version 24.0.6, build ed223bc
$ docker-compose -v
Docker Compose version v2.23.0-desktop.1
Make sure to install or upgrade them if necessary.
Add a Dockerfile to the project root, making sure to review the code comments:
# pull official base image
FROM python:3.12.0-slim-bookworm
# set working directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# add and install requirements
COPY ./requirements.txt .
RUN pip install -r requirements.txt
# add app
COPY . .
# run server
CMD python manage.py run -h 0.0.0.0
Here, we started with a slim-bookworm-based Docker image for Python 3.12.0. We then set a working directory along with two environment variables:
PYTHONDONTWRITEBYTECODE
: Prevents Python from writing pyc files to disc (equivalent topython -B
option)PYTHONUNBUFFERED
: Prevents Python from buffering stdout and stderr (equivalent topython -u
option)
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 a .dockerignore file to the project root as well:
env
Dockerfile
Dockerfile.prod
Like the .gitignore file, the .dockerignore file lets you exclude specific files and folders from being copied over to the image.
Review Docker Best Practices 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.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/usr/src/app
ports:
- 5004:5000
environment:
- FLASK_APP=src/__init__.py
- FLASK_DEBUG=1
- FLASK_ENV=development
This config will create a service called api
from the Dockerfile.
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 used -- 3.8
. Keep in mind that this version does not directly relate back to the version of Docker Compose installed; it simply specifies the file format that you want to use.
Build the image:
$ docker-compose build
This will take a few minutes the first time. Subsequent builds will be much faster since Docker caches the results. If you'd like to learn more about Docker caching, review the Order Dockerfile Commands Appropriately section.
Once the build is done, fire up the container in detached mode:
$ docker-compose up -d
Navigate to http://localhost:5004/ping. Make sure you see the same JSON response as before:
{
"message": "pong!",
"status": "success"
}
If you run into problems with the volume mounting correctly, you may want to remove it altogether by deleting the volume config from the Docker Compose file. You can still go through the course without it; you'll just have to re-build the image after you make changes to the source code.
Windows Users: Having problems getting the volume to work properly? Review the following resources:
You also may need to add
COMPOSE_CONVERT_WINDOWS_PATHS=1
to theenvironment
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 development environment:
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/usr/src/app
ports:
- 5004:5000
environment:
- FLASK_APP=src/__init__.py
- FLASK_DEBUG=1
- FLASK_ENV=development
- APP_SETTINGS=src.config.DevelopmentConfig # new
Then update src/__init__.py, to pull in the environment variables:
# src/__init__.py
import os # new
from flask import Flask, jsonify
from flask_restx 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 Ping(Resource):
def get(self):
return {
'status': 'success',
'message': 'pong!'
}
api.add_resource(Ping, '/ping')
Update the container:
$ docker-compose up -d --build
Want to ensure the proper config was loaded? Add a print
statement to __init__.py, right before the route handler, as a quick test:
import sys
print(app.config, file=sys.stderr)
Then view the Docker logs:
$ docker-compose logs
You should see something like:
<Config {
'ENV': 'development', 'DEBUG': True, 'TESTING': False,
'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=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': None, 'TRAP_BAD_REQUEST_ERRORS': None,
'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': None, 'JSON_SORT_KEYS': None,
'JSONIFY_PRETTYPRINT_REGULAR': None, 'JSONIFY_MIMETYPE': None,
'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093,
'RESTX_MASK_HEADER': 'X-Fields', 'RESTX_MASK_SWAGGER': True,
'RESTX_INCLUDE_ALL_MODELS': False
}>
Make sure to remove the print
statement before moving on.
✓ Mark as Completed