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.