Repository navigation
feat(compose): run sponsord, sponsord-onramp and Caddy behind profiles - #88
Merged
Merged
Conversation
Add the onboarding sponsor to the Compose deploy path as profiles in compose/compose.yaml: node, sponsord, onramp (also starts sponsord) and caddy. Each daemon keeps the node's hardening: host network with loopback listeners set in environment:, nothing published, image by digest, read-only rootfs, all capabilities dropped, a dedicated non-root account, and the units' stop graces. Credential files keep the roles' /etc/sponsord layout but belong to a sponsord account (bind mounts keep owner and mode; upstream rejects a group-readable keystore) and are mounted read-only one by one. The optional Caddy runs the official image as non-root with only NET_BIND_SERVICE and mirrors the role's Caddyfile. Compose interpolates disabled services, so no variable can be `:?`-required: an unset one now renders a value its service refuses (invalid image reference, unknown user, a domain with a non-numeric port). lint-compose renders every profile under env -i, twice: against compose/tests/invariants.jq with the example .env (allowed keys and exact mounts per service, exact security_opt, non-root users, loopback listeners, pinned secret files) and against compose/tests/fail-closed.jq with an empty one. It refuses an empty render and takes LINT_COMPOSE_FILE, not Compose's own COMPOSE_FILE. tests/scripts-test.sh adds a positive baseline and pins which invariant each of 47 broken variants trips. Breaking for existing Compose node operators: add COMPOSE_PROFILES=node to .env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The safety gate does not enforce that RPC secrets remain outside tracked Compose YAML.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds profile-based Docker Compose deployment for sponsord, sponsord-onramp, and optional Caddy alongside the existing node.
Changes:
- Adds hardened, digest-pinned Compose services and configuration examples.
- Introduces security and fail-closed invariant checks with negative tests.
- Updates operational documentation and CI guidance.
| File | Description |
|---|---|
compose/compose.yaml |
Defines profile-based services and hardening. |
compose/Caddyfile |
Adds TLS reverse proxy configuration. |
compose/.env.example |
Documents profiles, images, and service identities. |
compose/decdn.env.example |
Clarifies env-file quoting. |
compose/sponsord.env.example |
Adds non-secret daemon settings. |
compose/sponsord-secret.env.example |
Adds RPC secret template. |
compose/sponsord-onramp.env.example |
Adds public onramp settings. |
compose/tests/invariants.jq |
Validates rendered security invariants. |
compose/tests/fail-closed.jq |
Validates unset-variable behavior. |
compose/README.md |
Documents setup and operations. |
tests/scripts-test.sh |
Expands Compose regression variants. |
Makefile |
Reworks the Compose lint target. |
.github/workflows/ci.yml |
Updates Compose CI documentation. |
README.md |
Advertises expanded Compose support. |
docs/requirements.md |
Documents requirements and firewall ports. |
docs/lifecycle.md |
Updates Compose shutdown guidance. |
CONTRIBUTING.md |
Describes the expanded lint gate. |
AGENTS.md |
Records Compose architecture and conventions. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ponsor lint-compose's rendered environment has the env files merged in, so it could not tell a value written in the tracked compose.yaml from one in a root-only env file: an inline DECDN_RPC_URL or SPONSORD_RPC_URL passed. A third render with --no-env-resolution now checks compose/tests/inline-env.jq, an exact allow-list of the environment keys each service may set inline, with negative variants for both RPC URLs. The README's sponsor stop command left Caddy holding tcp/80 and tcp/443, answering with errors; it now stops caddy first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…olution The CI runner's Compose still merges the env files under `config --no-env-resolution` (and v2.33 does not know the flag), so the inline-env check saw every env-file key as inline and failed. Render with each service env file pointed at /dev/null instead: only keys written in compose.yaml remain, on any Compose release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thiras
added a commit
that referenced
this pull request
Oct 5, 2026
The squash of #88 predates the AGENTS.md follow-up, so main still described the inline-env render as `config --no-env-resolution`; it uses /dev/null env files. Also record: the caddy profile's operator-opened tcp/80 + tcp/443 in hard rule 2, compose/'s layout, that compose/Caddyfile is a hand-kept copy of the role's Caddyfile.j2, that the lint's allow-lists are deliberate friction tied to upstream sponsord's env names, and that CI's Compose is older than a dev box's (try new `config` flags under docker:27-cli first). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Adds the onboarding sponsor (
sponsord+sponsord-onramp) and an optional Caddy to the Docker Compose deploy path, as profiles incompose/compose.yaml:nodedecdn-nodesponsordsponsordonrampsponsord-onramp+sponsord(the onramp needs its daemon on the same host)caddyDesign
cap_drop: [ALL],no-new-privileges, a dedicated non-root account, and stop graces matching the units (120 s / 30 s).environment:, which beats env files. The release images default to0.0.0.0./etc/sponsord/layout, but are owned by a hostsponsordaccount. Bind mounts keep owner and mode, and upstream rejects a group-readable keystore. Each file is mounted read-only into/run/secrets/, and each container gets only the files it needs.NET_BIND_SERVICE.compose/Caddyfilemirrors the role's:admin off, h1/h2 only, notrusted_proxies.${VAR:?}: Compose interpolates disabled services too, so a required variable would break the other profiles. An unset variable now renders a value its service refuses: an invalid image reference, an unknown user, or a domain with a non-numeric port (the onramp and Caddy both reject it).Lint
make lint-composenow renders with--profile '*'underenv -i, twice:compose/tests/invariants.jq, with the example.env:privileged,pid: hostordevicesfail;security_optmust be exactlyno-new-privileges:true;commandorentrypoint;*_FILEpaths are pinned to/run/secrets/, and inline secrets are refused.compose/tests/fail-closed.jq, with an empty.env: checks that unset variables fail closed.Other changes to the target:
LINT_COMPOSE_FILEinstead of Compose's ownCOMPOSE_FILE.<service>: <invariant>.tests/scripts-test.shadds a positive baseline and pins which invariant each of 47 broken variants trips.Docs
compose/README.md: new sponsord section, upgrade note,sudo docker compose -f compose/compose.yamlthroughout.sponsord.env,sponsord-secret.env,sponsord-onramp.env. Contract addresses are left blank and point at the generatednetworks.yml.docs/requirements.md,docs/lifecycle.md, the rootREADME.md,AGENTS.md,CONTRIBUTING.mdand theci.ymlcomment.Test plan
make lint(pre-commit)make lint-compose, plus negatives for an empty render (docker shim) and shell-env isolationmake test-scripts: baseline + 47 compose variantsmake security-compose: 0 HIGH/CRITICAL; the MEDIUM/INFO findings are by design, as documentedcaddy validate/caddy fmtoncompose/Caddyfile, andcaddy adaptrejects the unset-domain defaultdecdn/sponsordHEAD, pushed to a loopback registry:0.0.0.0in its env file;/v1/info, listens on127.0.0.1:8080only, reads its files, and serves/healthzand/decdn.shover https through non-root Caddy;restart sponsordalso restarts the onramp, and an env-file change recreates the container onup -d.🤖 Generated with Claude Code