Skip to content

Instantly share code, notes, and snippets.

@koistya
Last active July 15, 2022 01:00
Show Gist options
  • Save koistya/08d3654862f654e756674491593e6fe2 to your computer and use it in GitHub Desktop.
Save koistya/08d3654862f654e756674491593e6fe2 to your computer and use it in GitHub Desktop.
Sample Docker-based web application setup

Docker-based Web Application Setup (example)

This is an example of hosting standalone web front-end (web) and data API (api) applications under the same domain via Nginx (acting as a reverse proxy) and Docker, where HTTP requests starting with example.com/graphql and example.com/login/* are being redirected to http://api:3000 and everything else under the same domain is going to be passed to http://web:3000.

Folder Structure

.
├── /nginx.sites/               # Server configuration for each of web apps
├── /nginx.snippets/            # Nginx code snippets
├── docker-compose.yml          # Defines Docker services, networks and volumes
└── nginx.config                # Top-level Nginx configuration

docker-compose.yml

version: '3'

volumes:
  data:
  redis:

services:

  nginx:
    image: nginx:1.11.10-alpine
    links:
      - api
      - web
    read_only: true
    tmpfs:
      - /var/cache/nginx
      - /var/log/nginx
      - /var/run
    volumes:
      - ./nginx.snippets:/etc/nginx/snippets:ro
      - ./nginx.sites:/etc/nginx/sites-enabled:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
      - /etc/ssl/certs/dhparam.pem:/etc/ssl/certs/dhparam.pem:ro
      - /var/www:/var/www
    ports:
      - '80:80'
      - '443:443'

  db:
    image: postgres:9.6.2-alpine
    restart: always
    environment:
      - POSTGRES_PASSWORD=xxx
    read_only: true
    tmpfs:
      - /tmp
      - /var/run/postgresql
    volumes:
      - data:/var/lib/postgresql/data

  redis:
    image: redis:3.2.8-alpine
    restart: always
    read_only: true
    volumes:
      - redis:/data

  web:
    image: web
    read_only: true
    restart: always
    environment:
      - PORT=3000
      - NODE_ENV=production
    expose:
      - '3000'

  api:
    image: api
    read_only: true
    restart: always
    depends_on:
      - db
      - redis
    links:
      - db
      - redis
    environment:
      - PORT=3000
      - NODE_ENV=production
      - NODE_DEBUG=false
      - WEBSITE_URL=https://example.com
      - FRONTEND_HOST_WHITELIST=localhost
      - DATABASE_URL=postgres://user:xxx@db:5432/api
      - DATABASE_DEBUG=false
      - REDIS_URL=redis://redis:6379/1
      - SESSION_SECRET=xxx
      - FACEBOOK_ID=xxx
      - FACEBOOK_SECRET=xxx
      - GOOGLE_ID=xxx
      - GOOGLE_SECRET=xxx
      - TWITTER_KEY=xxx
      - TWITTER_SECRET=xxx
    expose:
      - '3000'
    # ports:
    #   - '0.0.0.0:9229:9229' # V8 inspector

nginx.sites/example.com

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;
    include snippets/ssl-example.com.conf;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;
    include snippets/ssl-example.com.conf;
    root /var/www/example.com;

    location ~ /.well-known {
        allow all;
    }

    location ~ ^/login/.+ {
        include snippets/proxy-params.conf;
        proxy_pass http://api:3000;
    }

    location /graphql {
        include snippets/proxy-params.conf;
        proxy_pass http://api:3000;
    }
    
    location / {
        include snippets/proxy-params.conf;
        proxy_pass http://web:3000;
    }
}

nginx.snippets/proxy-params.conf

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_max_temp_file_size 0;
proxy_redirect off;
proxy_read_timeout 240s;

nginx.snippets/ssl-params.conf

# https://cipherli.st/
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Disable preloading HSTS for now.  You can use the commented out header line that includes
# the "preload" directive if you understand the implications.
#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
ssl_dhparam /etc/ssl/certs/dhparam.pem;

ssl-example.com.conf

include snippets/ssl-params.conf;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
@shadowbrush
Copy link

Thank you for providing this. I'd suggest removing /var/log/nginx from nginx.tmpfs. That way the nginx logs are sent to the docker logs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment