Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save cooljl31/3f94061a565e43061287461fdaa85f9f to your computer and use it in GitHub Desktop.
Save cooljl31/3f94061a565e43061287461fdaa85f9f 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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment