caddy: build via caddyserver.com download URL, not local xcaddy

The xcaddy/Go compile burns ~1GB RAM this VPS can't spare (per ~/docs/caddy.md
"Custom Binary"). Pull the prebuilt L4-enabled binary from the Caddy build
server instead and swap it over the stock binary in the official image. Built
and verified: caddy v2.11.3 with layer4.handlers.proxy + proxy_protocol.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Wayne Hayes 2026-06-03 22:39:33 -04:00
parent 2eb8a0c225
commit a9e2a736fc
4 changed files with 29 additions and 17 deletions

View File

@ -33,8 +33,10 @@ compose, config, ACL snippet, and Caddy build, and only *reads from the tailnet*
- **Mailbox** (`docker-compose.yml`): Stalwart in a Tailscale sidecar via - **Mailbox** (`docker-compose.yml`): Stalwart in a Tailscale sidecar via
`network_mode: service:ts-stalwart`. Binds nothing on the host. All mail `network_mode: service:ts-stalwart`. Binds nothing on the host. All mail
ports listen on the tailnet only. ports listen on the tailnet only.
- **Edge** (`caddy/`): a layer-4 TCP proxy. Pure pass-through; Stalwart owns - **Edge** (`caddy/`): a layer-4 TCP proxy (Caddy + `caddy-l4`, pulled prebuilt
TLS. **Can run on a different machine** than the mailbox — the key idea. from caddyserver.com — no local `xcaddy` build, per `~/docs/caddy.md`). Pure
pass-through; Stalwart owns TLS. **Can run on a different machine** than the
mailbox — the key idea.
- **Backends**: data+fts → Postgres, blob → Garage S3, lookup/in-memory → - **Backends**: data+fts → Postgres, blob → Garage S3, lookup/in-memory →
Redis. One stalwart role/db, one Garage bucket, one Redis logical DB. Redis. One stalwart role/db, one Garage bucket, one Redis logical DB.

View File

@ -17,7 +17,7 @@ tailwart/
├── docker-compose.yml # the mailbox: ts-stalwart sidecar + stalwart ├── docker-compose.yml # the mailbox: ts-stalwart sidecar + stalwart
├── config/config.toml # Stalwart config — PG + Redis + S3 wiring (strawman) ├── config/config.toml # Stalwart config — PG + Redis + S3 wiring (strawman)
├── caddy/ # the edge: custom Caddy (caddy-l4) layer-4 mail proxy ├── caddy/ # the edge: custom Caddy (caddy-l4) layer-4 mail proxy
│ ├── Dockerfile # xcaddy build with caddy-l4 + ratelimit │ ├── Dockerfile # pulls prebuilt caddy-l4 binary (caddyserver.com, no local build)
│ ├── caddy.json # :25/465/587/143/993 → stalwart over the tailnet │ ├── caddy.json # :25/465/587/143/993 → stalwart over the tailnet
│ ├── docker-compose.yml # deploy on any public-IP, tailnet, tag:reverse-proxy host │ ├── docker-compose.yml # deploy on any public-IP, tailnet, tag:reverse-proxy host
│ └── README.md │ └── README.md

View File

@ -1,15 +1,21 @@
# Custom Caddy with the layer-4 (TCP/UDP) app so it can proxy raw mail ports, # Caddy with the layer-4 (TCP/UDP) app so it can proxy raw mail ports.
# not just HTTP. caddy-ratelimit is included to match the house build on `box`. #
# Built the house way (see ~/docs/caddy.md "Custom Binary"): grab the prebuilt
# static binary from caddyserver.com's build server, NOT a local xcaddy/Go
# build. The compile burns ~1GB RAM, which this VPS can't spare — the download
# server does it for us. The base image only contributes its entrypoint + CA
# certs; we swap in the L4-enabled binary over the stock one.
# #
# docker build -t tailwart-caddy ./caddy # docker build -t tailwart-caddy ./caddy
# #
# Pinned to 2.11 to match box's Caddy. Bump deliberately. # Add more plugins by appending &p=<url-encoded module path> to CADDY_DOWNLOAD,
FROM caddy:2.11-builder AS build # e.g. ...&p=github.com%2Fmholt%2Fcaddy-ratelimit (the main box build has that).
RUN xcaddy build \
--with github.com/mholt/caddy-l4 \
--with github.com/mholt/caddy-ratelimit
FROM caddy:2.11 FROM caddy:2.11
COPY --from=build /usr/bin/caddy /usr/bin/caddy
# Proof the L4 module is in the binary (fails the build if not): ARG CADDY_DOWNLOAD="https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fmholt%2Fcaddy-l4"
RUN caddy list-modules | grep -q 'layer4' || (echo 'caddy-l4 missing!' && exit 1)
RUN apk add --no-cache curl \
&& curl -fsSL -o /usr/bin/caddy "$CADDY_DOWNLOAD" \
&& chmod 0755 /usr/bin/caddy
# Fail the build loudly if the L4 module isn't actually in the binary.
RUN caddy list-modules | grep -q 'layer4' || { echo 'caddy-l4 missing from binary!'; exit 1; }

View File

@ -17,11 +17,15 @@ proxying nginx/Caddy can do for *any* TCP — it just usually isn't used for it.
## Build & run ## Build & run
```bash ```bash
docker compose up -d --build # builds Dockerfile (xcaddy + caddy-l4), runs it docker compose up -d --build # builds the image, runs it
caddy list-modules | grep layer4 # (inside the image) proof the module loaded
``` ```
The build fails loudly if `caddy-l4` isn't in the resulting binary. The Dockerfile doesn't compile Caddy — it pulls the prebuilt L4-enabled binary
from `caddyserver.com/api/download` (the house method, see `~/docs/caddy.md`
"Custom Binary"), dodging the ~1GB-RAM local `xcaddy` build this VPS can't
afford. The build still fails loudly if `caddy-l4` isn't in the downloaded
binary. To add plugins, append `&p=<url-encoded module path>` to
`CADDY_DOWNLOAD` in the Dockerfile.
## Edit the upstream ## Edit the upstream