Guys, who can help set up Docker + Django + Gunicorn + Nginx?
I get an error "/etc/nginx/html/index.html" is not found (2: No such file or directory)
Or any other path
Hey there!
I recently done this setup on docker.
I used traefik as a reverse-proxy to django (gunicorn) / nginx and others services. Traefik makes it a lot easier to configure services with docker. And on my projects i only nginx as a static files server (i don’t find nginx configurations “friendly”).
So, here’s my setup for development (HTTP only). I use PDM as dependency/project manager, so the dockerfile below is suited for this.
# Dockerfile
FROM python:3.9
# set work directory
WORKDIR /app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN apt update -y
RUN apt install -y netcat gettext
RUN pip install pip setuptools wheel
RUN pip install pdm==2.1.0
COPY pyproject.toml pdm.lock manage.py /app/
# Install dev deps
RUN pdm install --no-lock --no-editable
COPY entrypoint.api.sh entrypoint.api.sh
RUN sed -i 's/\r$//g' entrypoint.api.sh
RUN chmod +x entrypoint.api.sh
ENV PYTHONPATH=src:.venv/lib/python3.9/site-packages
COPY ./ /app
ENTRYPOINT [ "sh", "/app/entrypoint.api.sh" ]
# docker-compose.yml
version: "3.3"
services:
traefik:
build:
dockerfile: traefik.Dockerfile
context: .
command:
- "--configFile=traefik.yml"
- "--providers.docker=true"
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./traefiklogs:/traefiklogs"
app:
build: .
volumes:
- .:/app/
- staticfiles:/app/static
ports:
- 80
env_file:
- .env.dev
labels:
traefik.enable: true
traefik.http.routers.api.rule: Host(`${HOST}`)
traefik.http.routers.api.entrypoints: http
command: python3 manage.py runserver 0.0.0.0:80
depends_on:
- traefik
nginx:
depends_on:
- traefik
- app
image: nginx:alpine
expose:
- 80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- staticfiles:/app/static
labels:
traefik.enable: "true"
traefik.http.routers.nginx.rule: Host(`${HOST}`) && PathPrefix(`/static`)
traefik.http.routers.nginx.entrypoints: http
traefik.http.services.nginx.loadbalancer.server.port: "80"
volumes:
staticfiles:
.:
# traefik.Dockerfile
FROM traefik:v2.4
COPY traefik.yml /etc/traefik/
# nginx.conf
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024; # increase if you have lots of clients
}
http {
include mime.types;
# fallback in case we can't determine a type
default_type application/octet-stream;
access_log /dev/null;
sendfile on;
server {
# if no Host match, close the connection to prevent host spoofing
listen 80 default_server;
return 444;
}
server {
listen 80;
client_max_body_size 4G;
server_name dev.boilerplate.com;
keepalive_timeout 5;
# path for static files
root /app/static;
location /static {
# checks for static file
alias /app/static;
}
}
}
# traefik.yml
global:
sendAnonymousUsage: false
providers:
docker: {}
entryPoints:
http:
address: ":80"
https:
address: ":443"
certificatesResolvers:
ssl:
acme:
httpChallenge:
entryPoint: http
email: levoagoraapp@gmail.com
storage: /letsencrypt/acme.json
accessLog:
filePath: "/traefiklogs/access.log"
format: json
fields:
defaultMode: drop
names:
ClientHost: keep
DownstreamStatus: keep
DownstramContentSize: keep
OriginDuration: keep
OriginStatus: keep
RequestMethod: keep
RequestPath: keep
RequestProtocol: keep
ServiceName: keep
StartUTC: keep
headers:
defaultMode: drop
names:
User-Agent: keep
Content-Type: keep
Basically, this configuration tells traefik to listen on a given HOST environment variable. This variable is set on my .env.dev file. Whenever a incoming request has a path /static this request is forwarded to nginx service that serves the static files, otherwise is forwarded to the app service. Nginx receives the static files from a volume shared with the app service. This directory is filled up with the static files with the collectstatic management command that is run on the app entrypoint. Here is the entrypoint:
# entrypoint.api.sh
#!/bin/sh
set -e
if [ "$DJANGO_SETTINGS_MODULE" = "app.settings.dev" ]
then
while ! nc -z $SQL_HOST $SQL_PORT; do
echo "Waiting for postgres... $SQL_HOST $SQL_PORT"
sleep 5
done
echo "PostgreSQL started"
fi
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py compilemessages -v 0
exec "$@"
After this, nginx contains all the static files to serve.
All of this code is available at github, it comes from my boilerplate project. I use this strategy when deploying apps on the cloud using virtual machines.
Let me know what you think about this!
Looks great, but I’d like to deal with Docker + Django + nginx + gunicorn. Without add-on services
