Back to Blog
Infrastructure5 min read

How to Host Multiple Apps on One VPS with Traefik and Custom Domains

DT
DomainDock Team
Engineering · May 24, 2026

A $6/month VPS can run five side projects, three APIs, a portfolio site, and a staging environment, all at the same time. Most developers don't know this because they think hosting multiple apps requires multiple servers or complicated load balancer configurations.

It doesn't. You need one tool: Traefik.

What Traefik Does

Traefik is a reverse proxy that sits in front of all your services. When an HTTP request arrives, Traefik reads the Host header, matches it against routing rules you've defined, and forwards the request to the correct backend process on the correct port.

One server. Multiple apps. Each with their own domain. Each with their own SSL certificate.

The Architecture

Internet
    │
    ▼
[Your VPS: IP 1.2.3.4]
    │
    ▼
Traefik (ports 80, 443)
    ├── portfolio.digitalweb.host → localhost:3001
    ├── api.digitalweb.host       → localhost:4000
    ├── staging.digitalweb.host   → localhost:8080
    └── client-demo.digitalweb.host → localhost:5000

Each app listens on a different port. Traefik maps each domain to the right port. SSL is handled by Traefik automatically; your apps only need to speak plain HTTP internally.

Prerequisites

  • A VPS with a public IP address (any cloud provider works: DigitalOcean, Hetzner, Vultr, Linode)
  • Docker and Docker Compose installed
  • Port 80 and 443 open in your firewall
  • A DomainDock account (free) for custom domain provisioning

Step 1: Set Up the Directory Structure

mkdir -p ~/traefik/dynamic ~/traefik/letsencrypt
touch ~/traefik/letsencrypt/acme.json
chmod 600 ~/traefik/letsencrypt/acme.json

The dynamic directory is where Traefik watches for routing rule files. acme.json is where Let's Encrypt certificates are stored; it must be readable only by root.

Step 2: Write the Traefik Static Configuration

Create ~/traefik/traefik.yml:

api:
  dashboard: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      email: you@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

providers:
  file:
    directory: /etc/traefik/dynamic
    watch: true

Key points:

  • Port 80 redirects to 443 automatically
  • Let's Encrypt certificates are issued via HTTP-01 challenge
  • The file provider watches the dynamic directory for routing config files

Step 3: Start Traefik with Docker Compose

Create ~/traefik/docker-compose.yml:

services:
  traefik:
    image: traefik:v3.3
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./dynamic:/etc/traefik/dynamic:ro
      - ./letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped

Start it:

cd ~/traefik && docker compose up -d

Traefik is now running and watching the dynamic directory for routing rules.

Step 4: Claim Your Custom Domains on DomainDock

This is where DomainDock plugs in. Instead of manually editing Cloudflare DNS records for each project, DomainDock handles it all from a dashboard.

  1. Log in to DomainDock
  2. Create a project for each app (or one project for all of them)
  3. For each domain, enter your VPS IP address and the port your app runs on
  4. DomainDock creates the DNS A record on Cloudflare automatically

Within 30–60 seconds, yourapp.digitalweb.host resolves to your VPS.

Step 5: Write Routing Rules for Each App

For each domain, create a YAML file in ~/traefik/dynamic/. Traefik hot-reloads these without restart.

~/traefik/dynamic/portfolio.yml:

http:
  routers:
    portfolio:
      rule: "Host(`portfolio.digitalweb.host`)"
      service: portfolio-svc
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  services:
    portfolio-svc:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:3001"

~/traefik/dynamic/api.yml:

http:
  routers:
    api:
      rule: "Host(`api.digitalweb.host`)"
      service: api-svc
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  services:
    api-svc:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:4000"

Repeat for each app. Traefik detects the new files and starts routing immediately.

Step 6: Deploy Your Apps on the Configured Ports

Each app just needs to listen on its assigned port. No SSL configuration needed, because Traefik terminates TLS before the request reaches your app.

Node.js example:

const app = express();
// ...your routes...
app.listen(3001, '127.0.0.1', () => {
  console.log('Portfolio running on port 3001');
});

Binding to 127.0.0.1 (loopback only) means your app isn't directly exposed to the internet; all traffic comes through Traefik first.

Automating with DomainDock

The manual routing rule approach above works, but it's repetitive. DomainDock's worker service can write Traefik config files automatically when you create or update a domain in the dashboard. This is the same workflow used internally for all domains hosted on the platform.

If you're running DomainDock's worker alongside Traefik, route config files are written to the dynamic directory automatically whenever a domain is provisioned, with no manual YAML editing.

Handling Multiple Apps in Docker

If your apps run in Docker containers, you can use Traefik's Docker provider instead of file-based routing:

services:
  myapp:
    image: myapp:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myapp.rule=Host(`myapp.digitalweb.host`)"
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
      - "traefik.http.services.myapp.loadbalancer.server.port=3000"

Traefik reads labels from running containers and generates routes on the fly. Add the traefik_network to both the Traefik container and your app containers so they can communicate.

Tips for Running Many Apps on One VPS

Keep apps stateless where possible. If your app writes to local disk, data is lost when the container restarts. Use an external database or object storage.

Use different port ranges per project type. For example: 3000–3099 for frontend apps, 4000–4099 for APIs, 8000–8099 for staging environments. Keeps ports from colliding.

Monitor with DomainDock health checks. Once your domains are created in DomainDock, the platform runs periodic health checks and shows status history. You'll know if a service goes down before your users tell you.

Use Docker restart policies. restart: unless-stopped in your Compose file means apps come back automatically after a server reboot.

Size your VPS correctly. A 1 GB RAM VPS can run 3–4 lightweight Node or Go services comfortably. Add 1 GB RAM for each heavyweight app (Next.js SSR, Python ML inference, etc.).

What This Costs

  • VPS: $4–$6/month (Hetzner CX11, DigitalOcean Basic, or Vultr Cloud Compute)
  • Domains: Free on DomainDock for *.digitalweb.host
  • SSL: Free via Let's Encrypt (Traefik handles renewal automatically)
  • Monitoring: Free on DomainDock

Total: $4–6/month for unlimited hosted projects with custom domains and SSL. That's less than one coffee.


Set up your first domain for free at DomainDock. Your apps are already running. They just need a clean address.

VPSTraefikreverse proxyDockerSSLinfrastructure
Newsletter

Developer infrastructure, in your inbox

DNS patterns, SSL guides, routing tips, and DomainDock updates. Monthly, no fluff.

No spam. Unsubscribe any time.