Blog / Tutorials

How to host multiple Docker containers on one cheap VPS

By the NoctHost TeamMay 27, 20268 min read

You do not need a server per app. One modest VPS can host a handful of small services side by side if you put a reverse proxy in front of them to route requests by hostname. Each app keeps its own container, its own data, and its own subdomain, while sharing one IP and one set of TLS certificates.

This guide uses Docker Compose plus Caddy. Caddy reads the Host header, sends traffic to the right container, and provisions a Let's Encrypt certificate for every domain automatically. You will run two demo apps and a shared proxy on one box.

What you'll need

  • A VPS on Ubuntu 22.04/24.04 with a dedicated IPv4.
  • Two or more subdomains with A records pointing at the IP (e.g. app1.example.com, app2.example.com).
  • Docker installed (see Step 1).

Step 1: Install Docker and open ports

curl -fsSL https://get.docker.com | sh
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Step 2: Plan the network

Every app and the proxy join one shared Docker network so Caddy can reach each container by its service name. The apps themselves do NOT publish ports to the host; only Caddy does. That keeps your apps off the public internet except through the proxy.

Tip — Never publish an app's port directly (no ports: section on the apps). If you do, anyone can hit it on http://your-ip:PORT and bypass TLS. Let the proxy be the only thing listening on 80/443.

Step 3: Write the compose file

Create /opt/stack/docker-compose.yml with a proxy and two example web apps. Whoami and a static nginx stand in for your real services; swap in your own images.

/opt/stack/docker-compose.yml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy-data:/data
    networks:
      - web

  app1:
    image: traefik/whoami
    restart: unless-stopped
    networks:
      - web

  app2:
    image: nginx:alpine
    restart: unless-stopped
    networks:
      - web

networks:
  web:

volumes:
  caddy-data:

Step 4: Route by hostname in the Caddyfile

Each domain block proxies to a container by service name and internal port. Caddy issues a certificate per domain on first request.

/opt/stack/Caddyfile
app1.example.com {
    reverse_proxy app1:80
}

app2.example.com {
    reverse_proxy app2:80
}

Step 5: Launch the stack

cd /opt/stack
docker compose up -d
docker compose ps

Visit each subdomain over HTTPS. app1 shows the whoami debug page; app2 shows the nginx welcome page. Adding a third app is three lines in compose plus one block in the Caddyfile.

Step 6: Watch resources on a small box

Containers share the host's RAM and CPU, so a tiny box can run out of memory if one app is hungry. Cap each service and keep an eye on usage.

docker stats --no-stream

Add limits per service in compose when needed:

    deploy:
      resources:
        limits:
          memory: 256m

Keep it running

  • restart: unless-stopped brings every container back after a reboot.
  • Update everything: docker compose pull && docker compose up -d.
  • Reclaim disk from old images: docker system prune -af.
  • If RAM gets tight on a small box, add a swap file as a safety net rather than oversubscribing.

This is the cheapest way to run several side projects at once: one box, one bill, hourly. NVMe storage keeps image pulls and container starts fast, and you can deploy a fresh server in about 60 seconds, then destroy it the hour your experiment ends.

Spin one up in about a minute

Email signup, pay with crypto, hourly billing. Trying a box costs cents — destroy it when you are done.

Deploy a server

Frequently asked

Caddy, nginx-proxy, or Traefik?
All three route by hostname. Caddy wins on simplicity because automatic HTTPS is built in and the config is tiny. Traefik shines when you want label-based dynamic discovery; nginx-proxy is a middle ground. For a handful of apps, Caddy is the least to maintain.
How many containers fit on a small VPS?
It depends entirely on each app's memory footprint, not a fixed count. Lightweight services (static sites, small APIs, a password manager) can stack a dozen on a 2 GB box. A single hungry Java app might need that whole box to itself. Watch docker stats.

Keep reading