How to Migrate Nginx to Docker with Traefik as a Reverse Proxy

traefik in front of nginx
traefik in front of nginx

1. Problem

I’m currently running Nginx on my Ubuntu server to host my Rails application. However, my app uses Kamal, which requires Traefik, and both Nginx and Traefik utilize ports 80 and 443. To simplify my setup, I’d like to migrate Nginx to a Docker container while preserving my existing configurations. How do I make Traefik in front of Nginx?

I plan to:

  1. Uninstall Nginx from Ubuntu without disrupting my setup.
  2. Seamlessly migrate my existing Nginx configuration to work with the Nginx Docker container.
  3. Backup all critical files, including my Nginx configurations, to avoid any data loss.
  4. Continue using my current PHP 7.4 socket (without switching to a PHP Docker image), maintaining compatibility without requiring manual configuration changes.
  5. Set up Traefik in front of Nginx so that Traefik handles ports 80 and 443, with Nginx operating smoothly behind it.

My goal is to ensure a smooth, efficient transition with minimal downtime, so everything works as expected without unnecessary effort.


2. Solving

2.1/ Stop nginx service

sudo service nginx stop;
sudo systemctl disable nginx; # prevent nginx start when reboot

2.2/ Backup config

# Create a backup directory
mkdir -p ~/nginx-backup

# Backup the Nginx configuration files
sudo cp /etc/nginx/nginx.conf ~/nginx-backup/
sudo cp -r /etc/nginx/sites-available ~/nginx-backup/
sudo cp -r /etc/nginx/snippets ~/nginx-backup/

2.3/ Uninstall Nginx (optional)

sudo systemctl stop nginx
sudo apt remove nginx nginx-common
sudo apt purge nginx

2.4/ Traefik deployed by kamal

Because I’m using Rails to deploy traefik by Kamal, here’s the deploy.yml, we can easily convert this file to docker-compose.yml

service: demo

# Name of the container image.
image: kokorolee/demo

servers:
  web:
    hosts:
      - meeaws_deployer
    labels:
      traefik.enable: true
      traefik.http.routers.demo.rule: Host(`shareopus.thnkandgrow.com`)
      traefik.http.routers.demo.entrypoints: websecure
      traefik.http.routers.demo.tls.certresolver: myresolver
    options:
      network: "traefik_network"

accessories:
  db:
    image: postgres:16.0
    host: meeaws_deployer
    env:
      clear:
        POSTGRES_USER: "demo"
        POSTGRES_DB: 'demo'
      secret:
        - POSTGRES_PASSWORD
        - POSTGRES_USER
    files:
      - config/deploy/init.sql:/docker-entrypoint-initdb.d/setup.sql
    directories:
      - data:/var/lib/postgresql/data
    options:
      network: "traefik_network"

# Credentials for your image host.
registry:
  username: kokorolee

  # Always use an access token rather than real password when possible.
  password:
    - KAMAL_REGISTRY_PASSWORD

# Inject ENV variables into containers (secrets come from .env).
# Remember to run `kamal env push` after making changes!
env:
  secret:
    - RAILS_MASTER_KEY
    - POSTGRES_PASSWORD
    - DB_HOST
    - POSTGRES_USER
    - PORT

# Use a different ssh user than root
ssh:
  user: deployer

# Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
traefik:
  image: traefik:v3.1
  options:
    volume:
      - "./.docker-data/traefik/letsencrypt:/letsencrypt"
    publish:
      - "443:443"
      - "8080:8080"
    network: "traefik_network"
  args:
    # api.insecure: true
    providers.docker: true
    providers.docker.exposedbydefault: false
    entrypoints.web.address: ':80'
    entryPoints.websecure.address: ':443'
    certificatesresolvers.myresolver.acme.tlschallenge: true
    certificatesresolvers.myresolver.acme.httpchallenge: true
    certificatesresolvers.myresolver.acme.httpchallenge.entrypoint: 'web'
    certificatesresolvers.myresolver.acme.email: '[email protected]'
    certificatesresolvers.myresolver.acme.storage: '/letsencrypt/acme.json'
# Configure a custom healthcheck (default is /up on port 3000)
healthcheck:
  path: /up
  port: 5000

This configuration allows Traefik to automatically manage SSL certificates, route traffic.

2.5/ Adding a Docker Compose File for Nginx

# docker-compose.yml
version: "3.3"

services:
  nginx:
    image: nginx:latest
    container_name: nginx
    restart: unless-stopped
    volumes:
      - /home/deployer/.docker-data/nginx/nginx.conf:/etc/nginx/nginx.conf                    # Nginx main config
      - /home/deployer/.docker-data/nginx/sites-available:/etc/nginx/sites-available          # Site configs for multiple domains
      - /home/deployer/.docker-data/nginx/sites-enabled:/etc/nginx/sites-enabled              # Symlinks for enabled sites
      - /home/deployer/.docker-data/nginx/snippets:/etc/nginx/snippets                        # PHP configs and snippets
      - /home/deployer/.docker-data/nginx/fastcgi.conf:/etc/nginx/fastcgi.conf
      - /var/www:/var/www                                       # Your web files
      - /var/run/php/php7.4-fpm.sock:/var/run/php/php7.4-fpm.sock          # PHP socket on the host
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nginx.rule=Host(`example.space`) || HostRegexp(`^.+.example.space$`) || HostRegexp(`^.+.thnkandgrow.com$`)"
      - "traefik.http.routers.nginx.entrypoints=websecure"
      - "traefik.http.routers.nginx.tls.certresolver=myresolver"
      - "traefik.http.services.nginx.loadbalancer.server.port=80"
      - "traefik.http.routers.nginx.priority=1"
    networks:
      - traefik_network

networks:
  traefik_network:
    external: true

This docker-compose.yml config allows Nginx to serve multiple hosts with SSL via Traefik, using domains like example.space and thnkandgrow.com, and all subdomains. It maps essential volumes for Nginx configurations (backed-up files) and PHP 7.4 socket.

The critical part is that both Nginx and Traefik must be on the same network (traefik_network) to enable proper routing and SSL handling.

The label traefik.http.routers.nginx.priority=1 sets the priority for the Nginx router in Traefik.

  • Purpose: It determines which router Traefik will use when multiple routers match a request.
  • Lower Priority: A lower number (like 1) means this router has a lower priority, so if other routers have a higher priority, they will handle the request first.
  • Use Case: This can be useful when you have multiple services or routers handling the same domain or subdomains, and you want to control which one takes precedence.

2.5/ Update some config to adapt new system

Updating Cloudflare SSL Settings to full, not Full (strict)

# sites-enable/blog.thnkandgrow.com

server {
   # ... current config
    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
       # ...other config
}
# /var/www/blog.thnkandgrow.com/wp-config.php
# add this line

if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') $_SERVER['HTTPS']='on'

Transitioning from an installed version of Nginx on Ubuntu to running it in a Docker container behind Traefik was a smooth and effortless process. By carefully backing up my configurations, leveraging Traefik for SSL, and updating my Docker and Nginx setups, I was able to ensure minimal downtime and a seamless transition.

If you’re considering a similar move, these steps should help make your migration painless. Happy deploying!

Thank you!

https://gist.github.com/kokorolx/2c2c72284b6945df47deedee8d2de950