2 min read

Hosting many projects, without the mess

Share:

Running more than one project on the same server can feel like juggling.

Each app wants its own port, SSL setup, and configs. Soon, youโ€™re copying snippets from StackOverflow just to keep things alive.

Thereโ€™s an easier way.

Itโ€™s called Caddy โ€” a web server that does two things very well:

  • Handles HTTPS for your domains automatically
  • Routes your domains to the right app with a few lines of config

Thatโ€™s it. No cron jobs for certs, no long nginx configs.

Two ways to use Caddy

There are two main ways you can run Caddy:

  1. Inside Docker (simple, good for one project)
  1. Once on the host, for everything (clean, good for many projects)

Letโ€™s look at both.

Caddy inside Docker (one project)

If youโ€™re running a single app and just want it live quickly, this is the easiest path.

File structure

project1/
 โ”œโ”€โ”€ frontend/
 โ”œโ”€โ”€ backend/
 โ”œโ”€โ”€ docker-compose.yml
 โ””โ”€โ”€ Caddyfile

docker-compose.yml

services:
  frontend:
    build: ./frontend
    expose: ["3000"]

  backend:
    build: ./backend
    expose: ["4000"]

  caddy:
    image: caddy:2
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

Caddyfile

project1.com {
    reverse_proxy frontend:3000
}

api.project1.com {
    reverse_proxy backend:4000
}

This way, Caddy runs as part of your project. It talks to frontend and backend by their service names.

Simple and contained. But once you add a second or third project, things start to get heavy.

Caddy on the host (for many projects)

When youโ€™re hosting multiple projects, the clean way is to install Caddy once on the server, and let it handle all domains.

Each project just exposes its app on a local port (like 3000 or 4000). Caddy forwards the right domain to the right port.

File structure

/etc/caddy/
 โ”œโ”€โ”€ Caddyfile
 โ””โ”€โ”€ sites/
      โ”œโ”€โ”€ project1.caddy
      โ”œโ”€โ”€ project2.caddy

/home/ubuntu/
 โ”œโ”€โ”€ project1/docker-compose.yml
 โ”œโ”€โ”€ project2/docker-compose.yml

/etc/caddy/Caddyfile

{
  email you@example.com
}
import /etc/caddy/sites/*.caddy

/etc/caddy/sites/project1.caddy

project1.com {
    reverse_proxy 127.0.0.1:3000
}

api.project1.com {
    reverse_proxy 127.0.0.1:4000
}

Now, adding a new project is straightforward:

  1. Run it on a new local port (say 3010).
  1. Add a new site file in /etc/caddy/sites/.
  1. Reload Caddy.
  1. Update DNS.

Done. The project is live with HTTPS.

Which one should you choose?

  • If you have one project โ†’ run Caddy inside Docker.
  • If you have many projects โ†’ install Caddy on the host, and keep site configs separate.

Why this works well

  • One Caddy for the whole server, not one per project.
  • Each project is private (only on localhost), only Caddy is public.
  • Configs are modular: one file per project.
  • Adding a new project doesnโ€™t break old ones.

The simple rule

Domain โ†’ Port

Thatโ€™s the only mapping you need to think about. Caddy takes care of everything else.