Skip to content

docs(docker): record socket-proxy operational behavior from a live Debian run - #96

Merged
tyler-rich merged 1 commit into
devfrom
claude/socket-proxy-ops-docs-cldj5a
Jul 26, 2026
Merged

docs(docker): record socket-proxy operational behavior from a live Debian run#96
tyler-rich merged 1 commit into
devfrom
claude/socket-proxy-ops-docs-cldj5a

Conversation

@tyler-rich

@tyler-rich tyler-rich commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

The wollomatic migration (#63) closed with one item open: boot the published image on a Docker-capable host and confirm DOCKER_GID is 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/json and /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 -allowGET pattern and the sidecar option set are untouched.

What changed

1. 999 is a convention, not a safe fallback. stat -c '%g' /var/run/docker.sock returned 989 on the host, so the Compose fallback would have failed outright. The README called 999 "the Debian/Ubuntu default", which reads as probably-right; it is now framed as a placeholder with the measured value cited. The stat derivation was already the instruction and remains it.

2. The -allowfrom source 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 -allowGET regex 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, and restart: unless-stopped restarts it into the identical failure under Docker's backoff. docker compose ps shows STATUS Restarting, never Exited — 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. curl reports 000, and list_images() raises DockerProxyError from httpx.HTTPError rather than from a non-200 response. Both paths surface as 502 on GET /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_on exists in any Compose file, for any service, in any profile. There is no condition: service_healthy gate, scrye never 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_GID note 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 — the docker-env "action required" note gains the derive-don't-trust-999 point and the crash-loop blast radius.
  • docs/ARCHIVE.md — dated 2026-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 — the DOCKER_GID comment still says "a docker group 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 with IMAGES=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.

…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.
@tyler-rich
tyler-rich merged commit 4305349 into dev Jul 26, 2026
4 checks passed
@tyler-rich
tyler-rich deleted the claude/socket-proxy-ops-docs-cldj5a branch July 26, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant