Skip to content

feat(images): unify the Nextcloud image foundation - #58

Open
henmohr wants to merge 5 commits into
mainfrom
feat/unified-image-foundation
Open

henmohr wants to merge 5 commits into
mainfrom
feat/unified-image-foundation

Conversation

@henmohr

@henmohr henmohr commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Addresses the first increment of #47 (Build reusable, testable, and maintainable Nextcloud container images).

One Dockerfile per image, both build channels:

  • .docker/app/Dockerfile now covers the stable ("release") and the development ("daily") channel through NEXTCLOUD_SOURCE instead of a separate version-specific Dockerfile. Dockerfile.35 is removed; the next Nextcloud major is expressed as build arguments, never as a new file, so a major bump does not add another implementation.
  • The development channel still verifies the daily tarball against its published .sha512 and now optionally asserts NEXTCLOUD_MAJOR.
  • Runtime tooling is shared by both channels, so stable and development images follow the same maintenance and security rules.

Traceability and reproducibility:

  • OCI labels (source, revision, created, version) plus LibreCode labels recording the resolved upstream base image and the Nextcloud major.
  • CI injects VCS_REF and BUILD_DATE so any running container can be traced back to the commit that produced it.
  • docker-php-extension-installer is pinned to a released version instead of "latest".

One reusable build path:

  • .github/actions/build-and-scan builds and scans a single image for linux/amd64 and linux/arm64, and is reused by every build channel.
  • The development workflow reuses it, so development images are now scanned with the same Trivy policy before being pushed and are built for both architectures like the stable ones.
  • The development workflow is version agnostic (workflow_dispatch inputs + a weekly schedule to keep following Nextcloud master).

Naming and tagging rules are documented in docs/images.md, together with the reuse policy for consuming repositories. New stable tag nc- allows consumers to pin a Nextcloud major; latest and sha- keep working exactly as before, and the bare tag is kept as a deprecated alias so existing environments do not break.

AGENTS.md records the hard rules and the roadmap so contributors and AI tools follow the same constraints.

Addresses the first increment of #47 (Build reusable, testable, and
maintainable Nextcloud container images).

One Dockerfile per image, both build channels:

- .docker/app/Dockerfile now covers the stable ("release") and the
  development ("daily") channel through NEXTCLOUD_SOURCE instead of a
  separate version-specific Dockerfile. Dockerfile.35 is removed; the
  next Nextcloud major is expressed as build arguments, never as a new
  file, so a major bump does not add another implementation.
- The development channel still verifies the daily tarball against its
  published .sha512 and now optionally asserts NEXTCLOUD_MAJOR.
- Runtime tooling is shared by both channels, so stable and development
  images follow the same maintenance and security rules.

Traceability and reproducibility:

- OCI labels (source, revision, created, version) plus LibreCode labels
  recording the resolved upstream base image and the Nextcloud major.
- CI injects VCS_REF and BUILD_DATE so any running container can be
  traced back to the commit that produced it.
- docker-php-extension-installer is pinned to a released version
  instead of "latest".

One reusable build path:

- .github/actions/build-and-scan builds and scans a single image for
  linux/amd64 and linux/arm64, and is reused by every build channel.
- The development workflow reuses it, so development images are now
  scanned with the same Trivy policy before being pushed and are built
  for both architectures like the stable ones.
- The development workflow is version agnostic (workflow_dispatch
  inputs + a weekly schedule to keep following Nextcloud master).

Naming and tagging rules are documented in docs/images.md, together
with the reuse policy for consuming repositories. New stable tag
nc-<major> allows consumers to pin a Nextcloud major; latest and
sha-<commit> keep working exactly as before, and the bare <major> tag
is kept as a deprecated alias so existing environments do not break.

AGENTS.md records the hard rules and the roadmap so contributors and
AI tools follow the same constraints.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Second increment of #47. A green build only proves an image compiles;
this proves it boots.

tests/smoke-test.sh starts the real stack on a throwaway Docker
network (postgres 16 + app + web) from the images this repository
builds, and asserts that:

- Nextcloud actually installs and `occ status --output=json` reports
  `"installed":true` (the official entrypoint performs the install from
  the runtime environment, exactly like a real deployment);
- the reported Nextcloud major matches the requested one, so a daily
  build cannot silently ship the wrong major;
- the HTTP front-end answers on `status.php` and reports the install;
- the OCI traceability labels are present on the app image.

The containers are named and aliased exactly like the real compose
stack (the web image resolves `app:9000`), so the test exercises the
contract the images are published with, not a simplified variant.
Everything is torn down on exit; `--keep` preserves the state for
debugging and failure logs are printed before exiting.

CI runs it on every change to `.docker/**` through
.github/workflows/image-smoke-test.yml, for both channels: the stable
channel follows `.env.example` (the same source of truth the publish
workflow uses) and the development channel accepts major/base inputs.

`make smoke-test` and `make smoke-test-dev` run the same check locally.
The scan step is what fails in CI today for both images: the web image
is untouched by this pull request and still fails, while the app job
only fails after the full build completes, so the failure is in the
shared scan step rather than in the build.

Two problems made that failure impossible to diagnose from the job
summary: the SARIF reports were only uploaded when the scan succeeded,
and the script exited silently with a bare status code. Neither told
the reader whether Trivy found vulnerabilities or the scanner itself
broke.

- Upload the SARIF reports with `if: always()` in every workflow, so a
  red build is diagnosable from the artifacts alone.
- Make scripts/scan-images.sh end with an explicit summary: which
  images failed the policy, which policy file was used and what the
  policy means. A successful scan now says so explicitly.
- Bump Trivy v0.74.0 -> v0.75.0 (released 2026-10-01). The
  vulnerability DB and the EOL detection evolve with the scanner, and
  an outdated scanner is a classic source of opaque failures. The
  policy in trivy.yaml is unchanged: a red scan still blocks
  publication.
- Document the pinned scanner version in README.md and record the
  "keep local and CI versions identical" rule in docs/images.md, so
  the next person does not have to guess which Trivy to install.
The scan fails on CVEs that are already fixed in the distribution
repository but not present in the published base image:

  libexpat  CVE-2026-93990   2.8.4-r0  -> 2.8.5-r0
  pcre2     CVE-2026-103111  10.48-r0  -> 10.49-r0

The upstream tag is not rebuilt the moment a security fix lands in
Alpine or Debian, so building straight from it ships vulnerabilities
that have a published fix. trivy.yaml only ignores vulnerabilities
without a fix, so these findings are correct and the images are what
has to change.

- web: `apk upgrade --no-cache` after FROM nginx:alpine
- app: `apt-get upgrade -y` in the shared tooling layer

Both channels get it, since the layer is shared by design.

Documented in docs/images.md together with the accepted consequence:
building the same commit at different times can produce different OS
package versions. That trade-off is deliberate; shipping known
vulnerabilities is the worse option. sha-<commit> tags stay immutable.

Also recorded the follow-up this exposes: without a scheduled rebuild,
published images go stale again and the scan fails the same way the
next time a CVE lands. Tracked in AGENTS.md, with the constraint that
scheduled runs must not rewrite sha-<commit> tags.
Publication is now behind a boot test, and both channels are refreshed
on a schedule so security fixes reach the published images without
waiting for a code change.

The pattern just seen in this repository is the one being fixed: a CVE
lands in the distribution repository, the upstream tag is not rebuilt,
the published image ships a vulnerability that already has a fix, and
the scan goes red on the next change. Rebuilding weekly breaks that
cycle at the source.

- docker-image.yml gains a weekly schedule and a `smoke` job that boots
  the real stack (postgres + app + web) and asserts the install, `occ
  status`, `status.php` and the OCI labels. `publish` needs it, so an
  image that compiles but does not boot never reaches the registry, on
  any run including the scheduled ones.
- nextcloud-development.yml runs the same smoke test against the exact
  development image it scanned, before pushing it. Same gate, and no
  duplicate build.
- Scheduled runs must not rewrite `sha-<commit>`: those tags are
  immutable by contract and a scheduled rebuild re-reads the
  distribution repositories, producing a different image for the same
  commit. They publish internal `build-<run_id>-<arch>` tags and only
  refresh the rolling tags (`latest`, `nc-<major>`, `dev*`).

The standalone image-smoke-test.yml workflow is removed: its job is now
performed by the lifecycle workflow itself, which avoids building the
images three times on every pull request while keeping the check where
it gates something. Local use is unchanged (`make smoke-test`,
`make smoke-test-dev`).

Docs updated: the `build-*` tag rule, the scheduled refresh contract
and the fact that the smoke test is a publication gate.
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