nginx Docker config to serve a public S3/MinIO Angular single-page-application over HTTP
This is my nginx
config which automatically serves a public S3/MinIO Angular single-page-application over HTTP. It is expected to be used in a configuration with an external proxy providing HTTPS
. In my case, this is Traefik. See Simple Traefik docker-compose setup with Lets Encrypt Cloudflare DNS-01 & TLS-ALPN-01 & HTTP-01 challenges for details on my configuration.
This config variant does not include config for service @angular/localize
configurations over S3. See How to use nginx to deliver Multi-Language I18N Angular UIs using @angular/localize for a nginx
example serving a static site with multiple languages and automatic selection logic.
nginx.conf
You typically only need to change the following variables here:
# Change this to your S3 bucket name. Make sure that is is set to public!
set $bucket "/my-bucket";
# Change this to your MinIO/S3 host (no scheme)
set $minio_host "minio.mydomain.com";
Full config:
# /etc/nginx/nginx.conf
worker_processes auto;
events { worker_connections 1024; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
# DNS via Docker host (Docker embedded DNS)
resolver 127.0.0.11 valid=24h ipv6=off;
resolver_timeout 5s;
# Optional gzip for text assets
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_types
text/plain text/css application/javascript application/json
application/xml application/rss+xml image/svg+xml;
# Disk cache (tune size as needed)
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:100m max_size=10g inactive=30d use_temp_path=off;
server {
listen 80;
server_name _; # Traefik routes here
# Convenience var for your bucket path
# Convenience vars for your bucket path and MinIO host
set $bucket "/my-bucket";
# Change this to your MinIO/S3 host (no scheme)
set $minio_host "minio.techoverflow.net";
# Intercept upstream 404s/403s and route to SPA
error_page 404 = @spa;
error_page 403 = @spa;
# Exact root "/" -> serve index.html
location = / {
proxy_pass https://$minio_host$bucket/index.html;
proxy_set_header Host $minio_host;
proxy_ssl_server_name on;
proxy_buffering on;
proxy_cache STATIC;
proxy_cache_key $scheme$proxy_host$uri$is_args$args;
# Do not cache SPA HTML in browsers (optional)
add_header Cache-Control "no-cache, must-revalidate" always;
# Intercept errors so error_page works even from cache layer
proxy_intercept_errors on;
# Avoid caching upstream errors
proxy_no_cache $upstream_status = 404;
proxy_cache_bypass $upstream_status = 404;
proxy_no_cache $upstream_status = 403;
proxy_cache_bypass $upstream_status = 403;
add_header X-Cache-Status $upstream_cache_status always;
}
# Unified handling for everything else
location / {
proxy_pass https://$minio_host$bucket$uri$is_args$args;
proxy_set_header Host $minio_host;
proxy_ssl_server_name on;
proxy_buffering on;
proxy_http_version 1.1;
proxy_cache STATIC;
proxy_cache_key $scheme$proxy_host$uri$is_args$args;
# Unified TTLs for successful responses
proxy_cache_valid 200 301 302 24h;
# Do not cache upstream errors (prevents serving plain NGINX 404)
proxy_no_cache $upstream_status = 404;
proxy_cache_bypass $upstream_status = 404;
proxy_no_cache $upstream_status = 403;
proxy_cache_bypass $upstream_status = 403;
# Optional browser caching (enable if you version assets)
# add_header Cache-Control "public, max-age=86400, immutable" always;
add_header X-Cache-Status $upstream_cache_status always;
# Make sure SPA fallback triggers on 404/403 from origin
proxy_intercept_errors on;
}
# SPA fallback target
location @spa {
proxy_pass https://$minio_host$bucket/index.html;
proxy_set_header Host $minio_host;
proxy_ssl_server_name on;
proxy_buffering on;
proxy_cache STATIC;
proxy_cache_key $scheme$proxy_host$uri$is_args$args;
add_header Cache-Control "no-cache, must-revalidate" always;
add_header X-Cache-Status $upstream_cache_status always;
}
}
}
docker-compose.yml
The docker-compose
config is pretty straightforward.
services:
nginx:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx_cache:/var/cache/nginx
labels:
- "traefik.enable=true"
- "traefik.http.routers.app-mydomain.rule=Host(`app.mydomain.com`)"
- "traefik.http.routers.app-mydomain.entrypoints=websecure"
- "traefik.http.routers.app-mydomain.tls.certresolver=cloudflare"
- "traefik.http.routers.app-mydomain.tls.domains[0].main=mydomain.com"
- "traefik.http.routers.app-mydomain.tls.domains[0].sans=*.mydomain.com"