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:
- Inside Docker (simple, good for one project)
- 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:
- Run it on a new local port (say 3010).
- Add a new site file in
/etc/caddy/sites/.
- Reload Caddy.
- 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.