Nginx + Docker cheatsheet.
Quick run
docker run -d -p 80:80 nginx
docker run -d -p 80:80 -v $(pwd)/html:/usr/share/nginx/html:ro nginx
docker run -d -p 80:80 -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro nginx
Custom Dockerfile
FROM nginx:1.27-alpine
# Custom config
COPY nginx.conf /etc/nginx/nginx.conf
COPY conf.d/ /etc/nginx/conf.d/
# Static content
COPY html/ /usr/share/nginx/html/
# Health
HEALTHCHECK --interval=10s --timeout=3s \
CMD wget --spider -q http://localhost/health || exit 1
Compose
services:
nginx:
image: nginx:1.27-alpine
ports: ["80:80", "443:443"]
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./conf.d:/etc/nginx/conf.d:ro
- ./html:/usr/share/nginx/html:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- app
restart: unless-stopped
ENV substitution (templates)
nginx official image: /etc/nginx/templates/*.template are envsubst’d at startup.
# default.conf.template
server {
listen 80;
server_name ${SERVER_NAME};
location / {
proxy_pass http://${APP_HOST}:${APP_PORT};
}
}
docker run -d \
-e SERVER_NAME=example.com \
-e APP_HOST=app \
-e APP_PORT=8000 \
-v $(pwd)/template.conf:/etc/nginx/templates/default.conf.template \
-p 80:80 \
nginx
reload after config change
docker exec nginx nginx -t
docker exec nginx nginx -s reload
Or use compose to volume-mount config + reload externally.
Watchtower for auto cert renewal
Cert renewals (via certbot in another container) write to a volume; nginx reloads:
services:
nginx:
volumes:
- certs:/etc/letsencrypt
certbot:
image: certbot/certbot
volumes:
- certs:/etc/letsencrypt
entrypoint: sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h; done'
Add a reloader script that runs docker exec nginx nginx -s reload after renewal.
Custom Dockerfile for static site
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Caddyfile alternative (TLS auto)
FROM caddy:2-alpine
COPY Caddyfile /etc/caddy/Caddyfile
COPY html /srv
Easier than nginx for auto-HTTPS, smaller config.
Run as non-root
FROM nginxinc/nginx-unprivileged:1.27-alpine
Variant that runs as UID 101, listens on 8080.
services:
nginx:
image: nginxinc/nginx-unprivileged:1.27-alpine
ports: ["8080:8080"]
Forwarding to compose service
upstream app {
server app:8000; # docker compose service name
}
Networks for service-name DNS.
hostnames inside container
Docker bridges typically not on host. Use host.docker.internal (Docker Desktop) or 172.17.0.1 (default bridge) to reach host services.
Logging
services:
nginx:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
By default, nginx official image symlinks access.log and error.log to stdout/stderr. Picked up by docker logs.
Healthcheck endpoint
location = /health {
access_log off;
add_header Content-Type text/plain;
return 200 "ok\n";
}
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
interval: 10s
timeout: 3s
Reverse proxy compose stack
services:
nginx:
image: nginx:1.27-alpine
ports: ["80:80", "443:443"]
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- certs:/etc/letsencrypt:ro
depends_on: [api, web]
api:
build: ./api
expose: ["8000"]
web:
build: ./web
expose: ["3000"]
expose makes ports available within the network without publishing to host.
Performance: bypass docker network for cheap
Use unix sockets across containers:
services:
nginx:
volumes: [socket:/run/sock]
app:
volumes: [socket:/run/sock]
command: gunicorn --bind unix:/run/sock/app.sock
volumes:
socket:
upstream app {
server unix:/run/sock/app.sock;
}
Common mistakes
- Mounting whole
/etc/nginxover config — wipes templates. - Forgetting
:roon config — accidental edits. - nginx user UID mismatch with mounted file owner.
default.confoverriding everything when you wanted incremental.- Logging to file inside container → vanishes on restart.
Read this next
If you want my nginx + docker compose stacks, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .