My encounter with Caddy
Gautier DI FOLCO July 26, 2023 [dev] #devops #infra #cloudAt some point in my previous position we had to set up live updates (i.e. getting changes without reloading the page) in our SPA.
Instead of relying on WebSockets I have chosen SSE as I was much simpler (as it is unidirectional) while perfectly fitting our use-cases.
Instead of building ours, we relied on Mercure, which itself relies on Caddy which is a "new" HTTP server.
While you don't really need to know it to have something working, we had come concerns which forced me to dig a little deeper, this log is about some snippets I collected.
The first awesome thing with Caddy
is its dynamical configuration.
Which means, once it runs (caddy run
), this command is enough to change the configuration:
$ curl localhost:2019/load \
-H "Content-Type: text/caddyfile" \
--data-binary @Caddyfile
So, with a simple definition:
http://localhost:8080 {
respond "Hello, world!"
}
(http://
here forces HTTP protocol, Caddy
if HTTPS by default)
We get
$ curl http://localhost:8080
Hello, world!
To be transparent, it often ends up in:
while inotifywait -e close_write Caddyfile test.sh; do ./test.sh; done
test.sh
containing the load curl
and the curl
s I want to test.
Caddy
gives you the opportunity to quickly create a reverse proxy:
http://localhost:8081 {
reverse_proxy :8080
}
You can be more specific adding requests matchers:
http://localhost:8081 {
reverse_proxy /api/* :8080
reverse_proxy /assets/* :8082
}
or having named matchers:
http://localhost:8081 {
@api_ro {
method GET
path /api/*
}
reverse_proxy @api_ro :8083
reverse_proxy /api/* :8080
reverse_proxy /assets/* :8082
}
Since I was running everything in kubernetes, I had to have a /healthz
endpoint to ensure container readiness/liveness:
http://localhost:8080 {
respond /healthz 200
}
At some point I also needed specific reverse proxy on another subdomain, involving CORS.
(cors) {
@origin{args.0} header Origin {args.0}
header @origin{args.0} Access-Control-Allow-Headers "content-type, x-requested-with"
header @origin{args.0} Vary Origin
}
localhost:8080 {
log
encode zstd gzip
import cors https://app.acme.crp
import cors https://backoffice.acme.crp
handle /healthz {
respond 200
}
handle /api {
rewrite * /v2/api
reverse_proxy https://backend.product.com {
header_up X-Real-IP {remote}
}
}
As you can see, you can easily factor-out directives.
I also use it extensively to prototype APIs (instead of Postman-like tools), as it allows me to have a mocked API code-first.