Skip to content

Docker fixture bootstrap is fragile for downstream consumers: proposals for a more composable install path #740

Description

@kojiromike

Context

Multiple downstream projects (this repo's harness, k8s charts, CI fixtures, security-test rigs) consume the openemr/openemr Docker image and end up reimplementing pieces of the install/bootstrap dance. The current image is great when used exactly as the canonical docker-compose example does, but small departures (bind-mounting sites/, init containers, headless CI) hit the same set of sharp edges. This issue proposes a coherent set of small, backwards-compatible upstream changes so downstreams stop reinventing the bootstrap.

Related: #500 (permissions on bind-mounted sites/), #562 (-c misrouted in auto_configure invocation). Both touch the same code path.

Root cause that motivated this

sites/default/ is simultaneously the seed of install-time files and the runtime install target. Bind-mounting a host directory over sites/ (which downstreams do for persistence, inspection, or test isolation) shadows the seed the entrypoint expects to copy from. The container then can't write sqlconf.php, but it also doesn't detect "fresh install needed" — it loops on Plan to try an upgrade from 0 to 7 and emits PHP Fatal error: Failed opening required '...sqlconf.php' on every subsequent CLI invocation. The failure is silent-ish (no exit 1), so health probes time out without a clear signal.

This is one of several symptoms of the broader pattern: the install path is entangled with the Apache entrypoint and auto-detects state instead of being addressable as a tool.

Proposed changes (all additive, all backwards compatible)

A. Relocate the install seed out of the runtime path

Move the shipped sites/default/ seed to e.g. /usr/share/openemr/sites-skel/default/. Entrypoint copies skel → sites/ only when sites/default/sqlconf.php is absent. Existing volume layouts are untouched; new bind-mounts stop self-destructing. This single change fixes the root-cause symptom above.

B. A real headless-install CLI

Expose php bin/openemr-install --from-env (or equivalent) that is idempotent and explicit:

  • Empty DB → install schema, write sqlconf.php, exit 0
  • Already installed → exit 0
  • Partial / inconsistent state → exit non-zero with a diagnostic; never silently fall through to upgrade

Today auto_configure.php is entangled with the Apache entrypoint. Splitting it out gives downstream init containers and CI steps a real tool to call without booting Apache. The existing entrypoint behavior stays the default.

C. Detect the bind-mount-shadow case and fail loudly

When the entrypoint sees install env vars set, sites/default/ empty, and the image's skel populated, either (a) copy from skel, or (b) print a one-line diagnostic pointing at A's docs and exit non-zero. Pure error-quality improvement; can't break anyone.

D. Ship a proper container healthcheck binary

/usr/local/bin/openemr-healthy that checks FHIR metadata and confirms install completed. Compose healthcheck: calls it. Replaces the hand-rolled curl ... || exit 1 loops every downstream writes.

E. Publish a pre-installed image tag

openemr/openemr:<version>-prebaked (or similar) that boots ready-to-serve with a known test admin account. New tag, doesn't touch existing ones. Eliminates the install dance entirely for CI fixtures — boot time drops from minutes to seconds. This is the highest-leverage change for downstream CI, but also the largest upstream lift (CI step to bake + publish).

Suggested sequencing

  1. A + C together — small PHP/shell change in the entrypoint, fixes the most common failure mode, no new surface area.
  2. D — trivial, replaces a pattern every downstream already implements.
  3. B — formalizes the install contract every downstream is currently reimplementing. Bigger design conversation.
  4. E — separate proposal; deserves its own discussion of tag conventions and rebuild cadence.

Happy to drive PRs for (A), (C), (D) once there's directional agreement here. (B) and (E) want a design pass first.

Why this is worth doing

The fragility is invisible to anyone using the canonical compose file untouched, but it taxes every other consumer in proportion to how serious their use case is — CI fixtures, k8s charts, security scanners, and dev harnesses all pay the same setup tax. Small upstream changes here remove that tax for many downstreams at once.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions