Postgres requires environment variables such as username, password, and database details to initialize the database. So, another file(.env.db) with the following information is out there.
Now, let us write the Dockerfile for the business logic(Django + unicorn). We will use a multi-stage builder approach with python:3.11.4-slim-buster as the parent image. In the builder section, dependencies and flakes are installed. The flake is a command-line utility that checks Python code against coding style (PEP 8), programming errors, and complex constructs.
############ BUILDER ############# pull official base imageFROM python:3.11.4-slim-buster as builder# set work directoryWORKDIR /usr/src/app# set environment variablesENV PYTHONDONTWRITEBYTECODE 1ENV PYTHONUNBUFFERED 1# install system dependenciesRUN apt-get update &&\
apt-get install -y --no-install-recommends gcc# lintRUN pip install --upgrade pipRUN pip install flake8==6.0.0COPY . /usr/src/app/RUN flake8 --ignore=E501,F401 .# install python dependenciesCOPY ./requirements.txt .RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt########## FINAL ########### pull official base imageFROM python:3.11.4-slim-buster# create directory for the app userRUN mkdir -p /opt/app# create the app userRUN addgroup --system app && adduser --system --group app# create the appropriate directoriesENVHOME=/opt/appENVAPP_HOME=/opt/app/webRUN mkdir $APP_HOMERUN mkdir $APP_HOME/staticfilesRUN mkdir $APP_HOME/mediafilesWORKDIR $APP_HOME# install dependenciesRUN apt-get update && apt-get install -y --no-install-recommends netcatCOPY --from=builder /usr/src/app/wheels /wheelsCOPY --from=builder /usr/src/app/requirements.txt .RUN pip install --upgrade pipRUN pip install --no-cache /wheels/*# copy entrypoint.prod.shCOPY ./entrypoint.sh .RUN sed -i 's/\r$//g'$APP_HOME/entrypoint.shRUN chmod +x $APP_HOME/entrypoint.sh# copy projectCOPY . $APP_HOME# chown all the files to the app userRUN chown -R app:app $APP_HOME# change to the app userUSER app# run entrypoint.shENTRYPOINT["/opt/app/web/entrypoint.sh"]
In the FINAL section of the build stage, an app user and its home directories are created. A couple of directories are made to store static and media files. The media files will be served from NGINX, which we will see later. A script named entrypoint.sh will run as the docker entry point. This script checks for the db container before starting the app.
It is a simple Dockerfile to the standards. To reduce the attack surface, the user is set to nginx instead of root. The parent image is chosen as nginx:1.27.
The nginx user requires permission to access a few directories to function correctly.
All we do differently is use a custom nginx.conf file as below. The requests are proxied to the app containers. The media files are stored at /opt/app/web/media.
Now, let us build our docker-compose file. Two volume directories are defined, one for Postgres and the other for media. The media_volume is attached to both NGINX and WEB services.
it builds Docker images for web and nginx services using the above Dockerfiles.
The app container starts following the DB. The env files are being passed to the respective containers.
List containers
Note
Well, the containers are up and running. Let us examine the docker volumes.
Docker Volumes
Tip
Next, we need to apply the Django changes to create the sessions on the database.
Significant, no errors. Now, let us try to access the application and upload an image.
Browse the application
Observability
Let’s look at the docker-compose logs to understand the flow. Nginx serves as a reverse proxy, sending requests to the app container upstream. The app retries the user details from the db before constructing the HTML page.
If you’d like to see the database tables, please refer to the following diagram. In the startup_image table, the image name is DevOps, and the description is Overview. The details match the figure above.