espocrm_daemon and espocrm_websocket exited 0 every six seconds while the main
container sat healthy for thirty-nine minutes, so the deploy waited out its retries
on
not converged: espocrm_daemon espocrm_websocket
espocrm_daemon.1 desired=Shutdown current=Complete
The service log shows our own gate working -- "[entrypoint] Exec: docker-websocket.sh"
-- and upstream's failing right after it. docker-*.sh call exitIfNotReady, which runs
`bin/command app-check` and exits 0 when that fails. CommandManager resolves the name
to an AppCheck class, and espocrm/espocrm carries no AppCheck.php at 10.0.6, so the
gate can never pass. isLegacy does not spare us either: it returns true only when
/var/www/html is itself a mount point, and we mount three subdirectories.
Only the broken half is replaced. applyConfigEnv stays, because the websocket service
is the only carrier of ESPOCRM_CONFIG_WEB_SOCKET_*, and those reach the shared
config.php nowhere else. Readiness is the caller's job already: --wait-ready blocks on
the marker the main container writes once its install has finished, which is a
strictly better-informed signal than app-check.
The logic lives in files/shell/sidecar_boot.sh rather than a bash -c string, following
the route this role already uses for docker-entrypoint-custom.sh and seed_config.php:
copied into the build context, COPYed and chmod'ed in the Dockerfile, addressed
through ESPOCRM_SCRIPT_SIDECAR_DOCKER. That buys shellcheck and bash -n coverage the
embedded string never had, drops one layer of quoting, and turns a missing argument
into a named error instead of a php invocation with no script.
ESPOCRM_APP_DIR replaces the second hardcoded /var/www/html in vars.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The path to the image entrypoint the build patches was written out four times in the Dockerfile, and /usr/local/bin twice more in vars. It now comes from ESPOCRM_BIN_DIR through ESPOCRM_ENTRYPOINT_SCRIPT_ORIGINAL, declared as a build ARG after FROM and passed from the compose build args. The runtime side already reads the same value from the environment, because a build ARG does not exist at runtime.
The sed expressions themselves are byte-identical. Only the file operand changed, and the variable sits outside the single quotes, so the delimiter and ampersand escaping that e272ae2d0 had to correct is untouched.
Not exercised: the four sed and grep pairs are build gates and there is no cheap target that builds a role image. A missing ARG would expand to nothing and leave sed without a file operand, which exits 4 with no input files, so the failure is loud at that layer rather than a silently broken image.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Dockerfile RUN that appends '|| true' to the upstream create-admin-user line used '|' as the sed delimiter, which collides with the '||' in the replacement (sed: unknown option to s), and left the '&' in '2>&1' unescaped (sed expands it to the whole match). Use '#' as the delimiter and escape the ampersand as '\&'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The upstream v10 entrypoint runs create-admin-user unguarded under
set -euo pipefail; on a persistent database that already holds the
admin (redeploy, DR recovery) it exits non-zero and aborts the install
before isInstalled is persisted, so every boot re-runs the install and
crash-loops. Append '|| true' the same way the adjacent set-password
line is already patched; the grep guard fails the build if upstream
moves the line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Upstream actionInstall() runs create-admin-user "$ESPOCRM_ADMIN_USERNAME"
but then pipes the password to a hardcoded 'set-password admin' (line 203,
verified in the 10.0.2 image); with our admin named 'administrator' the
silenced failure aborts the install under set -euo pipefail and loops the
container. Patch the line at image build time; the grep guard fails the
build if a future image reshapes it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stock image lays the app under /var/www/html from /usr/src at runtime, so the whole webroot landed on the shared NFS volume; on kernel-nfsd the www-data Apache workers on a non-populating swarm node get EPERM serving that root-owned code (403), and the stack never converges. Bake the code into the image at build time so /var/www/html is node-local on every replica, and share only data/, custom/ and client/custom over NFS. Move the boot-lock and ready-marker into the shared data/ so replicas still serialize the one-time install, keep the ready-marker check ahead of the lock (a serving replica must not re-enter the image entrypoint and rebuild the shared cache, which aborts 255), and chown the three shared dirs. Verified in compose (deploy + hard-restart + e2e); swarm eliminates cross-node code access by construction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dockerfiles have no suffix, so the comments lint never scanned them. Match the Dockerfile basename in the changed-targets collector and dispatch it to the hash-comment parser; changed-files-only semantics stay, so the legacy tree is unaffected until a Dockerfile is touched. Drop the remaining prose comment from the espocrm Dockerfile it immediately flagged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the replicas:1 pin and make the web tier genuinely scalable. The custom entrypoint now runs the image entrypoint's install/upgrade/config-seed exactly once cluster-wide: an atomic mkdir mutex on the shared volume root (flock is node-local on this NFS mount), the image init invoked with /bin/true so its final exec returns control, a ready marker for followers, stale-lock takeover past the orchestrator kill ceiling and TERM/INT release; every replica then starts apache itself. Previously the image entrypoint execed apache directly, so the seed/clear-cache block was dead code - it now runs as intended. PHP sessions move to the NFS-shared data/.sessions via a conf.d ini so OIDC/SAML handshakes survive replica hops. The cron daemon and the websocket service become explicitly pinned singletons (they only inherited the entity pin before). A missing image entrypoint after a version bump now fails loudly (upstream master restructured the entrypoint; 9.3.10 is verified). Converge retries 600, healthcheck start_period 15m, seeder debug env renamed out of the ESPOCRM_SEED_ config prefix scan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ENV approach failed at runtime because the ca-trust-wrapper does not
propagate container ENV variables when executing the wrapped ENTRYPOINT,
causing $ESPOCRM_ENTRYPOINT_SCRIPT_DOCKER / $SUITECRM_ENTRYPOINT_SCRIPT_DOCKER
to remain unexpanded ("not found" error in CI).
Replace with a RUN-time symlink (ln -sf) so the dynamic ARG path is baked
into the image filesystem layer. ENTRYPOINT now uses the fixed /entrypoint.sh
path, which is independent of ENV propagation and cache state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove quotes from FROM instruction in confluence, jira, suitecrm, espocrm Dockerfiles
(Docker does not strip quotes — quoted image references are invalid)
- Promote ENTRYPOINT_SCRIPT_DOCKER ARG to ENV in espocrm and suitecrm so the
runtime shell can resolve the path (ARG is not expanded in ENTRYPOINT exec form)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>