Skip to content

Refactor Linux CI to use pre-built Docker image from GHCR - #21972

Open
kadykov wants to merge 4 commits into
darktable-org:masterfrom
kadykov:ci-docker-build-environment-v2
Open

Refactor Linux CI to use pre-built Docker image from GHCR#21972
kadykov wants to merge 4 commits into
darktable-org:masterfrom
kadykov:ci-docker-build-environment-v2

Conversation

@kadykov

@kadykov kadykov commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Follows up on #19597 (devcontainer configuration).

What this changes

Replaces the per-run apt-get install steps in the Linux CI jobs with a pre-built Docker image hosted on GHCR. The image is built from .devcontainer/Dockerfile, which was introduced in the prerequisite PR and already mirrors the CI environment exactly.

Files changed

File Change
.github/workflows/build-docker.yml New build→test→push workflow: builds a candidate image from the Dockerfile, runs all four Linux CI matrix configurations against it via .github/scripts/test-image.sh, and pushes to GHCR only if every build succeeds
.github/scripts/test-image.sh New helper script that runs all Linux CI matrix configurations sequentially using docker run + .ci/ci-script.sh. Also callable locally for manual image validation
.github/workflows/ci.yml Linux jobs: use ghcr.io/darktable-org/darktable-build:latest; remove compiler and dependency install steps; permissions scoped to contents: read only
.devcontainer/devcontainer.json Switch from "build": (local Dockerfile build) to "image": (pull pre-built image — faster for devcontainer users)
.devcontainer/README.md Update CI environment section to document the build→test→push workflow and correct image update cadence
.ci/Dockerfile Removed — 2016-era Jenkins artefact, superseded by .devcontainer/Dockerfile
.gitignore Add install/ (created by test-image.sh during local runs)

Why

Single source of truth

.devcontainer/Dockerfile is the only place that defines the Linux build environment. ci.yml and the devcontainer both consume it. No more risk of drift between them.

Only tested images are published

The build-docker.yml workflow implements a build→test→push sequence. It verifies every Linux CI matrix configuration against the candidate image before updating :latest. An image that cannot build darktable is never published.

Faster CI feedback

The dependency install steps currently add several minutes to every Linux job. Pre-built images eliminate that overhead.

Easier contributor onboarding

devcontainer.json now uses "image": so opening the devcontainer is a docker pull rather than a full local build.

What does NOT change

  • Windows and macOS CI jobs — unchanged; containers are not applicable there
  • nightly.yml — the AppImage build deliberately targets ubuntu-22.04 for glibc compatibility and builds libs from source; it stays as-is
  • The CI matrix structure (compiler variants, build types, eco flags) — unchanged; compiler choice continues to be CC/CXX env vars

Image update cadence

:latest is updated only when .devcontainer/Dockerfile changes on master (triggering build-docker.yml automatically) or when a maintainer triggers a manual workflow_dispatch (e.g. to pick up upstream Ubuntu security patches). Between those events the image is frozen, which gives full control and reproducibility over the CI environment.

Image tagging strategy

Images are pushed as:

  • ghcr.io/darktable-org/darktable-build:latest — rolling, used by CI and devcontainer
  • ghcr.io/darktable-org/darktable-build:YYYY-MM-DD-SHORTSHA — pinned, for auditing

Notes

This PR replaces #21909 (same changes, cleaner git history — the earlier PR accumulated merge commits during development; please see that thread for full review context)

test-image.sh can also be used locally to validate a Dockerfile change before submitting a PR:

docker build -t darktable-build:candidate -f .devcontainer/Dockerfile .
.github/scripts/test-image.sh darktable-build:candidate "$PWD"

- Remove .ci/Dockerfile (2016-era Jenkins artefact, superseded)
- .devcontainer/devcontainer.json: switch to pre-built GHCR image
- .github/workflows/build-docker.yml: implement build→test→push workflow
  that builds from .devcontainer/Dockerfile, runs all Linux CI matrix
  configurations via .github/scripts/test-image.sh, and only pushes to
  GHCR if every build succeeds
- .github/workflows/ci.yml: Linux jobs pull ghcr.io/darktable-org/
  darktable-build:latest; permissions scoped to contents:read only
- .github/scripts/test-image.sh: new helper script that runs all four
  Linux CI matrix configurations (GNU16 Release, LLVM22 Release,
  GNU16 Debug, GNU16 Release+tests) against a candidate Docker image;
  also callable locally for manual validation
- .devcontainer/README.md: update CI environment section to describe
  the build→test→push workflow and correct the GHCR image update cadence
- .gitignore: add install/ (created by test-image.sh during local runs)
Copilot AI lite review requested due to automatic review settings August 23, 2026 17:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the Linux CI pipeline to run inside a pre-built Docker image published on GHCR (built from the devcontainer Dockerfile), removing per-run dependency installation and aligning CI + devcontainer on a single build-environment definition.

Changes:

  • Add a dedicated build→test→push workflow to build and validate a candidate image before publishing to GHCR.
  • Update Linux CI jobs to use ghcr.io/darktable-org/darktable-build:latest and drop in-job package installation.
  • Switch the devcontainer configuration and docs to consume the published image; remove the legacy .ci/Dockerfile; ignore locally-created install/ output.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
.github/workflows/build-docker.yml Introduces a build/test/publish pipeline for the CI image.
.github/scripts/test-image.sh Adds a helper script to run the Linux CI matrix against a candidate image.
.github/workflows/ci.yml Moves Linux CI jobs to a GHCR-hosted container image and removes apt install steps.
.devcontainer/devcontainer.json Switches devcontainer from local build to pulling the published image.
.devcontainer/README.md Updates documentation to reflect the GHCR-based CI image workflow and usage.
.ci/Dockerfile Removes an obsolete legacy Dockerfile.
.gitignore Ignores install/ artifacts created by local image testing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/build-docker.yml Outdated
Comment thread .github/scripts/test-image.sh Outdated
Comment thread .github/workflows/ci.yml
…docker-build workflow, authenticate in GHCR for pulling images in CI workflow
…patch

Add a pull_request trigger on .devcontainer/Dockerfile changes so that a
Dockerfile-modifying PR gets a full build-test cycle before merge.

The "Tag and push" step is guarded by `github.event_name != 'pull_request'`
so nothing is ever published to GHCR during a PR run.
@kadykov

kadykov commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Notes and caveats for reviewers

A few things worth knowing before this lands:

Splitting environment and code changes

With this approach, a PR that adds a new build dependency to the Dockerfile and code that uses it must be structured as two sequential PRs: first a Dockerfile-only PR (which, when merged, triggers build-docker.yml to build, test, and push the new :latest image), then the code PR on top. This is a deliberate trade-off for the "only tested images are published" guarantee. In practice Dockerfile changes are rare, so the extra step should be infrequent.

Image freshness — no automatic base-image updates

:latest is only updated when the Dockerfile changes on master or when a maintainer triggers workflow_dispatch. Security patches that land in the upstream ubuntu:26.04 image are not picked up automatically. Two options for addressing this in a follow-up PR:

  • Scheduled rebuild: add a schedule: trigger to build-docker.yml (e.g. weekly). Because the push step is gated by all CI matrix checks passing, a bad upstream update would block the push rather than silently breaking CI.
  • Dependabot + pinned digest: switch FROM ubuntu:26.04 to FROM ubuntu:26.04@sha256:<digest> and configure Dependabot for the Docker ecosystem. Dependabot opens a PR when the digest changes; build-docker.yml then validates the new base image automatically before merge (with the PR trigger we've now added). This gives the most control.

Either approach is a small follow-up; I'm happy to add a schedule: trigger to this PR if reviewers prefer, or leave it as a tracked future improvement.

What has been tested

  • ci.yml Linux jobs using the GHCR image: ✅ confirmed working (CI ran successfully on this PR)
  • build-docker.yml on pull_request: being verified via draft PR DO NOT MERGE: verify build-docker.yml PR trigger #21978 — a trivial Dockerfile comment triggers the full build+test cycle without pushing to GHCR
  • build-docker.yml on master push: will be confirmed only once this merges

@wpferguson

Copy link
Copy Markdown
Member

I have some questions...

  • How do we handle dependencies that rely on other dependencies?
  • How do we handle dependencies that build correctly but are broken?
  • How do we handle the situation where the first PR gets merged, the image is updated, and another PR runs CI before the second PR gets merged?
  • How do we handle devs whose workload doubles because of this?
  • How do we handle devs that don't want to mess with the dockerfile at all, but are now forced to

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.

3 participants