docs(docker): record socket-proxy operational behavior from a live Debian run - #96
Merged
Merged
Conversation
…bian run The wollomatic migration (#63) left one item open: boot the published image on a Docker-capable host and confirm DOCKER_GID is right for it. That run happened. The allowlist behaved exactly as documented -- digest-pinned image up under 65534:<gid> with a read-only rootfs, GET /images/json and /v1.NN/images/json 200, /info + /containers/json + /version 403, POST 405. Four operational behaviors it surfaced were not documented, or were documented wrongly: - DOCKER_GID was 989 on the host, not the 999 the Compose fallback assumes. The README called 999 "the Debian/Ubuntu default", which reads as probably-right; it is a packaging convention and was wrong here. Reframed as a placeholder, with the measured value cited. The stat derivation is unchanged. - The -allowfrom source check is evaluated before the path and method rules, so a request from a non-matching source gets 403 for everything, including methods that would otherwise answer 405. A wrong source and a disallowed path are indistinguishable from the client, which sends anyone debugging "403 on an endpoint that should work" at the -allowGET regex instead of the source check. The proxy log does distinguish them (blocked request ... forbidden IP). - A wrong GID crash-loops the sidecar rather than stopping it: it logs dial unix /var/run/docker.sock: connect: permission denied, exits, and restart: unless-stopped retries it under Docker backoff. STATUS reads Restarting, never Exited. The README said it "stays down". - From the client that crash loop is a connection error, not an HTTP status (curl reports 000); list_images() raises from httpx.HTTPError rather than from a non-200. Both surface as 502, so the detail text -- not the status code -- is what separates a broken sidecar from a restrictive allowlist. Also re-confirmed structurally that a failing proxy cannot block app startup: no depends_on exists in any Compose file, for any service, in any profile, so there is no service_healthy gate and the proxy healthcheck is consumed by nothing but itself. Scrye starts and stays up while the sidecar cycles. Docs only -- no code, Compose, CI, or configuration change; the -allowGET pattern and the sidecar option set are untouched. See docs/ARCHIVE.md § Deviations (2026-07-26), which also tracks the two non-doc spots left alone by that scope.
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
The wollomatic migration (#63) closed with one item open: boot the published image on a Docker-capable host and confirm
DOCKER_GIDis correct there. That run has now happened on a real Debian host with a live Docker daemon.The allowlist behaved exactly as documented — digest-pinned image up under
user: "65534:$DOCKER_GID"with a read-only rootfs,GET /images/jsonand/v1.NN/images/json→ 200,/info+/containers/json+/version→ 403,POST→ 405. No contradictions to fix there.What the run did surface is four operational behaviors that were either undocumented or documented wrongly. This PR writes them down. Docs only — no code, Compose, CI, or configuration change; the
-allowGETpattern and the sidecar option set are untouched.What changed
1.
999is a convention, not a safe fallback.stat -c '%g' /var/run/docker.sockreturned989on the host, so the Compose fallback would have failed outright. The README called999"the Debian/Ubuntu default", which reads as probably-right; it is now framed as a placeholder with the measured value cited. Thestatderivation was already the instruction and remains it.2. The
-allowfromsource check is evaluated before the path and method rules. A request from a non-matching source gets 403 for everything — including methods that would otherwise answer 405 — so a wrong source and a disallowed path are indistinguishable from the client. Anyone debugging "403 on an endpoint that should work" will suspect the-allowGETregex when the cause is the source check. The proxy's own log does distinguish them (blocked request … forbidden IP), so the docs now say to read it before touching the pattern.3. A wrong GID crash-loops the sidecar; it does not exit cleanly. It logs
dial unix /var/run/docker.sock: connect: permission denied, exits, andrestart: unless-stoppedrestarts it into the identical failure under Docker's backoff.docker compose psshows STATUSRestarting, neverExited— the README previously said it "stays down", which sends an operator looking for a stopped container.4. From the client, that crash loop is a connection error, not an HTTP status.
curlreports000, andlist_images()raisesDockerProxyErrorfromhttpx.HTTPErrorrather than from a non-200 response. Both paths surface as 502 onGET /api/docker-environments/{id}/images, so the detail text — not the status code — is what separates a broken sidecar from a working-but-restrictive allowlist.Structural check: a failing proxy cannot block app startup
Verified rather than assumed — grepped repo-wide, no
depends_onexists in any Compose file, for any service, in any profile. There is nocondition: service_healthygate,scryenever waits on the proxy at startup, it is never torn down when the proxy fails, and the proxy's healthcheck is consumed by nothing but itself. Scrye starts and stays up while the sidecar cycles, so a permission error at proxy start means "docker-env is unavailable", not "Scrye is broken". Documented; nothing needed changing.Files
README.md— § Configuration (DOCKER_GIDnote and the wrong-GID failure paragraph), § Optional sidecars (derive-don't-guess, new "reading a 403 from the proxy" bullet), § Security model (source check runs first).CHANGELOG.md— thedocker-env"action required" note gains the derive-don't-trust-999 point and the crash-loop blast radius.docs/ARCHIVE.md— dated2026-07-26§ Deviations entry covering all four findings, the confirmed-as-documented list (this retires the 2026-07-24 "still to do on a Docker-capable host" item), and the structural re-confirmation.Deliberately not changed
Scoped docs-only, so two spots still carry the softer wording. Both are recorded in the archive entry as follow-up for a change that may touch non-doc files:
docker/docker-compose.yml— theDOCKER_GIDcomment still says "adockergroup of 999 is the Debian/Ubuntu default" (config file).backend/app/core/docker_proxy.py— the non-200 error message still advises checking the proxy is "read-only withIMAGES=1", a stale tecnativa env-var reference that has been wrong since the wollomatic migration (code).See
docs/ARCHIVE.md§ Deviations (2026-07-26) for the full record.