How to Deploy a Hardened Nginx Container with Docker Compose
On this page
Containers make deployments repeatable, but default settings expose more than necessary. This guide serves a static site with Nginx using Docker Compose, with read-only content, a localhost-only port, health monitoring and automatic restarts.
Prerequisites
- Linux host with Docker Engine and the Compose plugin installed from Docker's official instructions
- A user in the
dockergroup, orsudo - Verify with
docker --versionanddocker compose version
Step-by-Step Guide
Step 1: Create the project
mkdir -p ~/webapp/site cd ~/webapp cat > site/index.html <<'EOF' <!DOCTYPE html> <html lang="en"><head><meta charset="utf-8"><title>Hello</title></head> <body><h1>Served by Nginx in Docker</h1></body></html> EOF
Step 2: Write compose.yaml
services:
web:
image: nginx:1.27-alpine
ports:
- "127.0.0.1:8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
- 127.0.0.1:8080 keeps the port private. Publish it only through a reverse proxy with HTTPS.
- :ro mounts the content read-only inside the container.
- no-new-privileges stops processes from gaining extra privileges.
Step 3: Start and verify
docker compose up -d docker compose ps curl -I http://127.0.0.1:8080
You should see HTTP/1.1 200 OK, and the service should report healthy after a short delay.
Step 4: Read logs and update
docker compose logs --tail 50 web docker compose pull docker compose up -d
Conclusion
You have a reproducible web container that is not directly reachable from the internet. Update pinned image versions regularly, scan images for known vulnerabilities, remove unused images with docker image prune and put TLS termination in front of the service.