Skip to content

Bake a pinned Flarum into the image instead of resolving it at first boot #2

Description

@karl-bullock

Problem

The image does not contain Flarum. Dockerfile has zero references to flarum/flarum — it is a PHP 8.3 + nginx + Composer runtime plus a boot script. The forum is resolved from Packagist on first container start, and nothing about that resolution is pinned:

Where What Constraint
entrypoint.sh:186 flarum/flarum ^2.0 with --stability=beta
entrypoint.sh:274 fof/upload *
entrypoint.sh:275 fof/redis *
entrypoint.sh:276 fof/horizon *
entrypoint.sh:277 flarum/realtime *
entrypoint.sh:280 flarum/audit *
entrypoint.sh:281 flarum/extension-manager *

There is no composer.json or composer.lock in the repository, so nothing records what a given image is supposed to install.

What that costs

  • The same image tag produces different forums. Seven floating constraints, one of them accepting prereleases. Two people running docker compose up a week apart get different code, and so does the same person redeploying.
  • docker pull gets you no Flarum. First boot needs working Packagist access. An outage, a yanked release, or a network-restricted host means no install — and there is no offline or airgapped path at all.
  • Image tags cannot express a Flarum version. :1.0.0 versions the wrapper. There is no way for a user to pin the thing they actually care about, and no way for us to say "this image is Flarum 2.0.3".
  • No rollback. Rebuilding a previous tag does not reproduce the previous forum, because resolution happens at run time, not build time.
  • A broken upstream release breaks new installs retroactively, including installs of images that were fine yesterday.
  • The README oversells it. It says "There is no runtime download of a setup script". Literally true — no setup script is fetched — but the entire application and six extensions are. A reader comparing images will take that sentence to mean the opposite of what happens.

Proposal — pinned skeleton baked at build time

Same approach already used for the Warble tenant images: resolve once, commit the lockfile, bake the result, copy into the volume on first boot.

  1. Commit composer.json + composer.lock describing Flarum plus the six bundled extensions, at explicit versions.
  2. Build time: composer install --no-dev from that lockfile into /opt/flarum-skeleton. The image now genuinely contains Flarum.
  3. First boot: if the data volume is empty, copy the skeleton into /var/www/html and continue with the existing install path (flarum install, migrations, enable extensions). If the volume already has a forum, do nothing — same as today.
  4. Tagging: image tags become <flarum-version>-rN (e.g. 2.0.3-r0), so the tag states what you get. TARGET_FLARUM_VERSION stays as an escape hatch for people who want to float deliberately.
  5. Updating: bump the lockfile in a PR. roundtrip proves the new resolution installs and restores before it ships, which is strictly better than discovering it in production.

The Extension Manager keeps working: the volume is still a writable Composer project with a real composer.json, it is only seeded from the image instead of resolved from the network.

Acceptance criteria

  • Dockerfile references flarum/flarum; docker create + docker export shows Flarum present with no container ever started
  • Two boots of the same image tag yield identical composer.lock hashes in the volume
  • First boot succeeds with no network access to Packagist (the current blocker for airgapped installs)
  • Extension Manager can still install and remove an extension from the admin UI
  • roundtrip still passes, including restoring an older backup into the newer version
  • Existing volumes from pre-change images still boot untouched
  • README's "no runtime download" wording corrected to describe what actually happens

Risks / open questions

  • Image size grows by the vendor tree (tens of MB). Acceptable, and the tradeoff should be stated in the README rather than hidden.
  • Upgrade path for existing deployments needs deciding: does a user on a running volume get the new skeleton on image update, or stay put until they act? Staying put is safer and matches current behaviour, but means the image version and the forum version can diverge — which partly re-opens the problem this issue exists to close.
  • Lockfile drift — worth a scheduled job or Renovate to open the bump PR, otherwise the pin quietly becomes staleness.
  • --stability=beta should not survive this change unless there is a reason; if Flarum 2.0 stable is out, pin stable.

Non-goals

  • Not changing how extensions are enabled, how extend.php is generated, or the backup/restore flow.
  • Not removing TARGET_FLARUM_VERSION.
  • Not bundling additional extensions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions