Nginx in Docker Compose

services: some-nginx: image: nginx:1.31 ports: - "8080:80" volumes: - /some/content:/usr/share/nginx/html:ro

In Docker Compose, the official nginx image serves the folder mounted at /usr/share/nginx/html: the line /some/content:/usr/share/nginx/html:ro under the service’s volumes, read-only because nginx only reads it. In a Compose file, a relative path such as ./content resolves against the file’s own directory.

The docker run command, converted

The static-content command from the image’s Docker Hub page, with the port it publishes in its next example and the tag pinned:

docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d -p 8080:80 nginx:1.31
services:
  some-nginx:
    image: nginx:1.31
    ports:
      - "8080:80"
    volumes:
      - /some/content:/usr/share/nginx/html:ro

Configuration from the environment

The default site is /etc/nginx/conf.d/default.conf; mounting a file at /etc/nginx/nginx.conf replaces the main configuration instead. Since 1.19 the image also reads /etc/nginx/templates/*.template at startup, runs them through envsubst, and writes the result to /etc/nginx/conf.d without the suffix:

services:
  web:
    image: nginx:1.31
    ports:
      - "8080:80"
    volumes:
      - ./content:/usr/share/nginx/html:ro
      - ./templates:/etc/nginx/templates:ro
    environment:
      NGINX_HOST: example.com
      NGINX_PORT: "80"
    healthcheck:
      test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://localhost/"]
      interval: 10s
      timeout: 3s
      retries: 3
# templates/default.conf.template
server {
    listen       ${NGINX_PORT};
    server_name  ${NGINX_HOST};
    root         /usr/share/nginx/html;
    add_header   X-Host $host;
}

Only variables set in the container’s environment are replaced, so nginx’s own $host stays as it is. NGINX_PORT has to match the container side of ports, here 80. Both the Debian and Alpine variants of 1.31 include curl, so the healthcheck works on either.

Gotchas

  • A content folder without index.html answers / with 403, and curl -f turns that into an unhealthy container.
  • After editing a mounted config, docker compose exec web nginx -t checks it and docker compose exec web nginx -s reload applies it without dropping connections. Templates are only rendered at startup, so a changed template needs docker compose restart web.
  • nginx:1.31 is the mainline branch and nginx:1.30 the stable one; nginx:latest follows mainline.
  • As a reverse proxy, nginx reaches other services by name: proxy_pass http://app:3000; in a template, with app listed under depends_on.

Try it in the docker run to Compose Converter

compose.yaml

1 service · 1 thing left out

Left out, and why

  • -dCompose starts services in the background already, so this flag needs no key.

Runs in your browser. Nothing you enter is sent anywhere.