Profile

    Hosting Multiple Domains on One Server with Docker, NGINX & SSL (DevOps for Developers)

    By Muhammad Sharjeel | Published on Sun Jul 13 2025

    Hosting Multiple Domains on One Server with Docker, NGINX & SSL (DevOps for Developers)

    Why This Was Worth Learning

    As a full-stack developer, I usually focus on building features — not server orchestration. But recently, I needed to deploy three independent services (Alara, PolyX, Langflow) on a single cloud server. Each had its own domain name, microservices, databases, and required HTTPS.

    This felt like a DevOps task. But instead of offloading it, I decided to learn it. Here's exactly how I pulled it off with Docker, NGINX, and Certbot.

    Problem Statement

    • One server (GCP VM)
    • Three services: Alara, PolyX, Langflow
    • Each must be accessible from its own domain:
      • https://alara-agents.com
      • https://poly-x.com
      • https://langflow.app
    • Each service uses microservices (Node.js, Redis, MongoDB)
    • Each service is containerized with Docker
    • All domains must have valid SSL certificates (Let's Encrypt)

    Solution Architecture

    I used a centralized NGINX reverse proxy pattern:

    1. One main NGINX container exposed on ports 80 and 443
    2. Each app stack runs its own Docker services (on custom ports)
    3. Domains are routed via NGINX server_name blocks
    4. SSL certificates are handled via Certbot and mounted into NGINX

    Folder Structure

    /projects
      ├── nginx-proxy/
      │   ├── docker-compose.yml
      │   └── conf.d/
      │       ├── alara.conf
      │       ├── polyx.conf
      │       └── langflow.conf
      ├── alara/
      │   └── docker-compose.yml
      ├── polyx/
      │   └── docker-compose.yml
      └── langflow/
          └── docker-compose.yml

    Docker Networking Strategy

    I created a shared Docker network:

    docker network create shared

    Each app and the NGINX container are connected to this shared network. This allows NGINX to proxy to containers by name (e.g., alara-api:3000).

    Central NGINX Setup

    Here’s a snippet from my main NGINX container’s compose file:

    services:
        nginx:
          image: nginx:latest
          ports:
            - "80:80"
            - "443:443"
          volumes:
            - ./conf.d:/etc/nginx/conf.d
            - /etc/letsencrypt:/etc/letsencrypt
            - /var/www/certbot:/var/www/certbot
          networks:
            - shared

    NGINX Domain Config (alara.conf)

    server {
        listen 80;
        server_name alara-agents.com;
      
        location /.well-known/acme-challenge/ {
          root /var/www/certbot;
        }
      
        location / {
          return 301 https://$host$request_uri;
        }
      }
      
      server {
        listen 443 ssl;
        server_name alara-agents.com;
      
        ssl_certificate /etc/letsencrypt/live/alara-agents.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/alara-agents.com/privkey.pem;
      
        location / {
          proxy_pass http://alara-api:3000;
          include proxy_params;
        }
      }

    I repeated this setup for poly-x.com and langflow.app as well.

    App Port Mapping (Avoiding Conflicts)

    Each service stack maps its internal ports to unique external ones. For example:

    Alara:
        3100:3000 (API)
        3101:3001 (Auth)
        3102:5000 (Alerts)
      
      PolyX:
        3200:3000
        3201:4000
        3202:5000
      
      Langflow:
        3300:3000
        3301:5000

    MongoDB and Redis also get isolated ports per service. This eliminates any conflicts.

    SSL via Certbot

    I used Certbot in webroot mode (so it works behind NGINX):

    docker run --rm -it \
        -v /etc/letsencrypt:/etc/letsencrypt \
        -v /var/www/certbot:/var/www/certbot \
        certbot/certbot certonly \
        --webroot --webroot-path=/var/www/certbot \
        -d alara-agents.com -d poly-x.com -d langflow.app

    Certificates are automatically mounted into the NGINX container.

    Auto-Renewal

    I set up a cron job to renew certificates and reload NGINX monthly:

    0 3 * * * docker run --rm \
        -v /etc/letsencrypt:/etc/letsencrypt \
        -v /var/www/certbot:/var/www/certbot \
        certbot/certbot renew --webroot --webroot-path=/var/www/certbot \
        && docker exec nginx-proxy nginx -s reload

    Result: 3 Domains, 1 Server, Full HTTPS

    • https://alara-agents.com → Alara stack
    • https://poly-x.com → PolyX stack
    • https://langflow.app → Langflow stack

    All isolated, independently scalable, and SSL-secure — running on a single server.

    Key Takeaways

    • Use one NGINX reverse proxy to handle all domain routing
    • Keep Docker networks shared but services isolated
    • Use Certbot in webroot mode and mount volumes into NGINX
    • Map ports uniquely per app to prevent service collisions

    Final Thoughts

    This experience taught me that as a full-stack dev, understanding DevOps makes you 10× more effective — especially when working solo or deploying to production. This setup is now part of my go-to deployment playbook.

    Hope it helps you as much as it helped me!

    DevOpsDockerNGINXSSLLet's EncryptDeployment