2 min read

Zero downtime deploys with Caddy

One line in Caddy holds requests while a Docker container is replaced, so a deploy looks like a slow page instead of a 502.

Zero downtime deploys with Caddy
Isaac Bythewood Isaac Bythewood
2026-10-03

If you run your sites with Docker Compose behind Caddy then a deploy with docker compose up -d --build stops the old container before it starts the new one. For a moment there's nothing listening and Caddy answers anyone who shows up with a 502. For a small app that's maybe a second but anything that takes a while to shut down, like an app holding open server-sent event streams, can leave you with 502s for 10 or 15 seconds.

Simon Willison wrote a TIL on this back in 2021 after Matt Holt pointed him at lb_try_duration. It's meant for load balancing across several upstreams but it works fine with just one. When Caddy can't reach the upstream it keeps retrying for that long instead of giving up, so the request waits on the new container and then gets its page:

example.com {
	reverse_proxy app:8000 {
		lb_try_duration 40s
	}
}

You might worry about a POST being sent twice but Caddy's defaults already handle it. A failed dial is retried for any method since nothing reached the app, and a request that fails after it reached the app is only retried if it's a GET.

I went with 40 seconds since Docker's stop_grace_period is 30 seconds on my containers, so a replace can't take longer than that. The downside is that if a site really does crash a visitor waits 40 seconds before they see the 502, which I'm fine with for a handful of personal sites.

That's it, one line of config and deploys don't drop requests anymore.


Some posts in similar tags to this one.

It's Go all the way down
It's Go all the way down
Every process in front of my websites is a Go binary now, from the tunnel to the web server to the sites themselves, and the whole stack runs on a desktop I already owned for a few dollars a month in power.
Isaac Bythewood Isaac Bythewood
2026-08-30
An agentic loop in Go for a small local model
An agentic loop in Go for a small local model
I run my own assistant on a 9B at home and the tool calling loop in it is a few hundred lines of Go, most of which is there to stop it offering to look something up instead of looking it up.
Isaac Bythewood Isaac Bythewood
2026-09-20
Why not make your own dashboard in 2026
Why not make your own dashboard in 2026
I was checking the same six tabs all day out of habit so I built one page with all of it on it, and it only took a couple of evenings.
Isaac Bythewood Isaac Bythewood
2026-09-10