eSign dev-box container startup failures (nginx non-root PID) — 2026-06-01
Summary
During InstaCash 2026-05-27 devel-update testing on the
ledereradev box, theesign_cssandesign_osscontainers came upunhealthy. Root cause is an image/infra regression, not the InstaCash update code: nginx runs as non-roottechteamer(uid 1000) but the bakednginx.confwrites its PID to root-owned/run, so nginx can’t start, supervisord marks itFATAL, and the container healthcheck flipsunhealthy. An ephemeralsedfix unblocks the box; the durable fix must be baked into the esign images. Two other findings here are red herrings or dev-tooling-only (RedisStore, chalk). Plus: how to actuallysshto this box from Claude’s shell.
Dev box only — host has since MOVED (2026-07-01)
Observed on the old rootless-docker box reached as
Facekom(=lederera@localhostviaProxyJump FKJumpBox), now decommissioned. Current host:fk-devTailscale VM — see dev-build-host and §5 below. These are dev-box infra/image gotchas surfaced while testing the InstaCash update — not product bugs in the update branches. See dev-box-cv-photo-processing-failures for the sibling gotchas from the same testing wave.
1. PRIMARY — esign_css & esign_oss nginx won’t start (non-root PID file)
This is an IMAGE regression, not the InstaCash update
Both containers were
unhealthypurely because nginx could not start. The node app, redis, and cron supervisord programs were allRUNNING. nginx was the only failure. Nothing in the instacash-2026-05-27 branches caused this.
Symptom
docker psshowsesign_cssandesign_ossasunhealthy.- nginx fails at startup with:
nginx: [emerg] open("/run/nginx.pid") failed (13: Permission denied) supervisorctl statusinside the container:nginx→FATAL Exited too quickly (process log may have details)node/ app,redis,cron→ allRUNNING
Root cause
Both containers run images:
harbor.techteamer.com/facekom-devel/esign_css:2024.4.1-20240614
harbor.techteamer.com/facekom-devel/esign_oss:2024.4.1-20240614
For Agents
Despite the
20240614tag, these images were rebuilt ~2025-12-08 and now ship nginx 1.28. The tag is misleading — do not assume the image is old just from the date in the tag.
The chain:
- The image runs as non-root user
techteamer(uid 1000). - Baked
/etc/nginx/nginx.confline 6 haspid /run/nginx.pid;. /runisroot:root drwxr-xr-x— not writable bytechteamer.- nginx cannot create the PID file →
[emerg] open("/run/nginx.pid") failed (13: Permission denied)→ nginx exits immediately. - supervisord marks nginx
FATAL ("Exited too quickly"). - The container healthcheck (see §2) sees a non-
RUNNINGprogram → containerunhealthy.
Immediate (ephemeral) fix — applied 2026-06-01
Rewrite the PID path to a techteamer-writable location and restart nginx via supervisord. Run per container (esign_css, esign_oss):
docker exec -u 0 <container> sed -i 's#^pid .*nginx.pid;#pid /tmp/nginx.pid;#' /etc/nginx/nginx.conf
docker exec <container> supervisorctl restart nginxVerified: both containers went healthy after this.
Lost on container recreate
/etc/nginxis NOT bind-mounted (only/workspace/<svc>, the certs, and/var/logare). So thissededit lives only inside the running container’s writable layer and is wiped ondocker rm/ recreate (it survives a plaindocker restartsince that keeps the layer, but not a recreate). It’s a stopgap to unblock the box, not a fix.
Durable fix — bake into the images
The fix has to land in the esign_css and esign_oss images / Dockerfiles, because /etc/nginx is not bind-mounted from the host checkout. Pick one:
- Bake
pid /tmp/nginx.pid;(or anothertechteamer-writable path, e.g. under/workspaceor/var/run/techteamer) into the image’snginx.conf. ← simplest chown techteamer /run/nginx.pid(orchmod/own/run) at build time so the default path works.- Run nginx as root in the image (least preferred — reverses the non-root hardening).
Do NOT try to fix this in the host checkout
Editing
/workspace/esign_cssor/workspace/esign_osson the box does nothing for nginx — the config is baked at/etc/nginx/nginx.confinside the image. The fix belongs in the Dockerfile / image build, then a re-pull on the dev box.
2. Container healthcheck logic (why a restart = 60s of unhealthy)
The healthcheck script is /usr/local/bin/supervisor-health-check.sh. It marks the container unhealthy if either:
- any supervisord program is not
RUNNING, OR - any program has uptime matching
0:00:[0-5][0-9]— i.e. up < 60 seconds (an anti-flap guard against a program that keeps crash-looping just under the check window).
Docker healthcheck params: interval 60s, retries 3, start-period 60s.
For Agents — expected behaviour after any restart
Any
supervisorctl restart <program>(including the nginx fix above) makes the container reportunhealthyfor ~60 seconds while the restarted program’s uptime climbs back over 60s, then it recovers tohealthyon its own. This is the anti-flap rule firing, not a second failure. Don’t chase it — wait out the minute and re-check.
3. RED HERRING — RedisStore is not a constructor (esign_css)
Already resolved in the code — only bites a stale node_modules
This error did not occur on the dev box (the esign_css node server was
RUNNING). It surfaces only when running against an old (≤ v6, factory-style)connect-redis.
The code is correct for connect-redis v9 at server/web/web-server.js:24:
const { RedisStore } = require('connect-redis');
// ...
new RedisStore({ client });The container’s /workspace/esign_css/node_modules is host-bind-mounted and has connect-redis@9.0.0 — matching the v9 API. So there’s no mismatch on the box.
RedisStore is not a constructor is the classic failure when the installed connect-redis is the old factory-style API (const RedisStore = require('connect-redis')(session)), i.e. a stale node_modules.
General lesson for devel updates — re-
yarn installafter a dependency major bumpAfter a dependency major bump lands in a devel-update, the running container must be re-
yarn installed against the updatedpackage.json/lockfile. A baked-image install can be stale and won’t reflect the bump. Because/workspace/<svc>/node_modulesis host-bind-mounted, the install state on disk is what matters — not what the image baked. If you seeRedisStore is not a constructor(or similar “X is not a constructor/function” after a major bump), suspect a stalenode_modulesfirst.
4. chalk ^5 ESM-only regression (dev tooling only — yarn trans)
Test/dev tooling only — not runtime
This breaks the
yarn transtranslation-check script, not any server. Fixed in the local mac checkout.
esign_oss/bin/test/trans-check.js:6 did:
const chalk = require('chalk');Under chalk v5 (ESM-only) this throws (require() of an ESM module). Fix = convert to a dynamic import('chalk') inside the existing promise chain:
- line 6 changed from
const chalk = require('chalk')tolet chalk; - chalk is loaded in the existing
.then(...):import('chalk').then(m => { chalk = m.default; /* ...rest of chain... */ })
The fix is in the LOCAL checkout, not on the box
Applied to
/Users/levander/coding/facekom/esign_oss/bin/test/trans-check.js. The dev box’s/workspace/esign_ossis a separate checkout — ifyarn transneeds to run on the box, the same change must be made there too.
5. SSH access to the dev box from Claude Code’s shell
OUTDATED HOST (2026-07-01) — the
Facekom/FKJumpBoxtopology below is DEAD
ssh Facekom(lederera@localhostviaProxyJump FKJumpBox→root@lederera-447-fk-hardver) is decommissioned (offline since ~2026-06-27). The dev host is now thefk-devTailscale VM:command ssh ops@fk-dev.taild4189d.ts.net(100.91.108.61, Tailscale SSH, no keypair, no jump box). See dev-build-host. The topology table is kept only as a record of the old box.
STILL TRUE — in Claude's shell, use
command ssh(akakuwrapper shadows plainssh)The user’s interactive shell has a
kakufunction that aliasesssh/scpto_kaku_wrapped_ssh. That function is not loaded in a non-interactive shell, so plainsshfails with_kaku_wrapped_ssh: command not found. Bypass withcommand ssh/command scpto get the raw binary.
Old SSH topology (from ~/.ssh/config) — historical, host offline:
| Host alias | Resolves to | Notes |
|---|---|---|
Facekom | lederera@localhost via ProxyJump FKJumpBox, key ~/.ssh/id_ed25519 | Was the rootless-docker host. DEAD. |
FKJumpBox | root@lederera-447-fk-hardver | Bare Alpine jump hop (no docker, no lederera user). DEAD. |
Working invocation (current host)
Use a login shell (bash -l) so docker is on PATH:
command ssh ops@fk-dev.taild4189d.ts.net bash -l -s <<'EOF'
docker ps
# ... docker / supervisorctl commands here ...
EOFContainers on the dev host: esign_css, esign_oss, vuer_oss, vuer_css, postgresql, rabbitmq, janus, nginx_proxy (crash-loops on fk-dev, bypassed by the Tailscale sidecars), plus vuer_cv on the old box (fk-dev is non-GPU; CV deferred).
Quick recipe
esign_css / esign_oss showing
unhealthyon the dev box?
sshin correctly:command ssh ops@fk-dev.taild4189d.ts.net bash -l -s <<'EOF' ... EOF(plainsshis shadowed bykaku; use a login shell so docker is on PATH). (The oldssh Facekombox is decommissioned — dev-build-host.)- Find the failing program:
docker exec <container> supervisorctl status. If nginx isFATALand the app/redis/cron areRUNNING, it’s the PID-permission bug.- Confirm:
docker logs <container>(or nginx’s supervisord log) showsopen("/run/nginx.pid") failed (13: Permission denied).- Unblock (ephemeral):
docker exec -u 0 <container> sed -i 's#^pid .*nginx.pid;#pid /tmp/nginx.pid;#' /etc/nginx/nginx.confthendocker exec <container> supervisorctl restart nginx. Wait ~60s (healthcheck anti-flap, §2) and re-check — it’ll gohealthy.- Durable: bake the PID path (or
/runownership) into the esign images —/etc/nginxis not bind-mounted, so the host checkout can’t carry it (§1).- If you see
RedisStore is not a constructor: stalenode_modules, not this bug — re-yarn installin/workspace/<svc>(§3).
Related
- instacash-update-2026-05-27-status — the devel-update wave being tested when these failures surfaced; per-repo conflict topology.
- dev-box-cv-photo-processing-failures — sibling dev-box gotchas (vuer_cv stopped + hairpin NAT, single-file bind-mount
/etc/hosts) from the same InstaCash testing session; same “fix-is-ephemeral, re-apply / bake it” theme. - sms-verification-code-dev-testing — another dev-box testing gotcha from the same nrt self-service wave.
- esign — Electronic Signature System (esign_css / esign_oss) overview.
- esign-css-customization-branches — esign_css customization fleet + standard dev/test method.
- esign-css-instacash-orphan-history — esign_css InstaCash branch shape (orphan history).
- dev-environment — FaceKom dev-environment reference (containers, supervisord, dev-box layout).
- devel-update-workflow — the devel → customization sync workflow this testing belongs to.
- infrastructure — dev-box networking, host-networking model, nginx subdomain routing.
- vuer_docker — container orchestration /
dockeroperations.