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