Skip to content

Retire the parallel CI image pipeline and adopt the liminal-hq shared images #221

Description

@ScottMorris

Summary

Liminal Notes maintains its own parallel CI image pipeline — .github/workflows/build-ci-images.yml building three targets (test, desktop, android) from docker/ci/Dockerfile.ci-base, published as ghcr.io/scottmorris/liminal-notes-ci-base-*. The liminal-hq shared images now cover everything those images provide, plus more (Bun, GStreamer base+bad, fonts-noto-color-emoji, xvfb, gh, cargo-nextest, a pinned Rust 1.96.1). Retire the parallel pipeline and adopt the shared tiers:

Current image Contents (from Dockerfile.ci-base) Replacement
liminal-notes-ci-base-test Ubuntu 24.04, Node 20, pnpm 10.28.2, build-essential, python3 ghcr.io/liminal-hq/ci-web:latest (Node 24, pnpm 10.28.2, Bun 1.3.14)
liminal-notes-ci-base-desktop (amd64+arm64) + Tauri GTK/webkit stack, rustup stable ghcr.io/liminal-hq/tauri-ci-desktop:latest (amd64+arm64, Rust 1.96.1 pinned, GStreamer/xvfb/emoji fonts, tauri-cli)
liminal-notes-ci-base-android + JDK 17, Android SDK 36, build-tools 36.0.0, NDK 27.1.12297006 ghcr.io/liminal-hq/tauri-ci-mobile:latest (JDK 17, SDK 36, NDK 28.2, Android Rust targets)

Concrete wins: the entire build-ci-images.yml workflow, docker/ci/Dockerfile.ci-base, and three GHCR packages stop needing maintenance; image contents (toolchain bumps, security refreshes, the scheduled YYYYMMDD rebuilds) are handled centrally; and the desktop image gains the pieces this repo's own image lacks (GStreamer, xvfb, emoji fonts, a pinned toolchain instead of floating stable). The hand-rolled .devcontainer/Dockerfile — which independently re-creates what tauri-dev-mobile ships, including the portable-AppImage tauri-cli fork — can be replaced by the shared dev image too.

Current state

  • .github/workflows/build-ci-images.yml — builds/pushes ghcr.io/scottmorris/liminal-notes-ci-base-{desktop,android,test} on pushes touching the Dockerfile (desktop is linux/amd64,linux/arm64; android and test are amd64).
  • docker/ci/Dockerfile.ci-base — three-stage build as summarised above. Notably: Node 20 (NODE_MAJOR=20), pnpm 10.28.2, Rust installed as floating stable under /usr/local/rustup, NDK 27.1.12297006, and python3 in the test target.
  • .github/workflows/test-ci.yml:
    • packages-tests, desktop-lint-typecheck, desktop-tests, desktop-coverage-summary — all in ghcr.io/scottmorris/liminal-notes-ci-base-test:latest with pnpm/action-setup@v4 + actions/setup-node@v4 (.node-version = 20.19.6), pnpm run ci:install, then the respective ci:* scripts.
    • rust-tests — in ghcr.io/scottmorris/liminal-notes-ci-base-desktop:latest; awk-resolves the channel from rust-toolchain.toml (pins 1.93.0) and installs it via dtolnay/rust-toolchain@stable, rust-cache on apps/desktop/src-tauri -> target, pnpm run ci:test:rust.
  • .github/workflows/publish-desktop.yml:
    • publish-linux — matrix ubuntu-24.04 / ubuntu-24.04-arm, container liminal-notes-ci-base-desktop:latest, same toolchain-resolve dance, then tauri-apps/tauri-action@v0.6.1 with TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: "true".
    • publish-windows — native windows-latest, unaffected.
  • .github/workflows/build-android.ymlbuild-android job in liminal-notes-ci-base-android:latest; pnpm run ci:android:prepare then EAS profile builds (ci:android:build:{preview,development,production}).
  • .github/workflows/publish-site.yml — plain runner, Pages; unaffected.
  • rust-toolchain.toml pins 1.93.0 (no components listed).
  • package.json engines: "node": ">=20.19.6 <21"; packageManager: pnpm@10.28.2; .node-version = 20.19.6.
  • .devcontainer/Dockerfile — hand-built devcontainer: full Tauri apt list incl. GStreamer/xvfb/emoji fonts, JDK 17, Node 20, gh, Android SDK 36 + NDK 28.2.13676358, rustup stable, and cargo install tauri-cli --git ... --branch feat/truly-portable-appimage. This is functionally ghcr.io/liminal-hq/tauri-dev-mobile re-implemented locally.

Migration steps

  1. Bump rust-toolchain.toml to the shared pin so container jobs use the preinstalled toolchain:

    [toolchain]
    channel = "1.96.1"
  2. In .github/workflows/test-ci.yml, swap containers. JS jobs (packages-tests, desktop-lint-typecheck, desktop-tests, desktop-coverage-summary):

    Before:

    container:
      image: ghcr.io/scottmorris/liminal-notes-ci-base-test:latest
      credentials:
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    After:

    container:
      image: ghcr.io/liminal-hq/ci-web:latest

    Keep pnpm/action-setup and setup-node — the repo is pinned to Node 20.19.6 (engines: >=20.19.6 <21) while the image ships Node 24, and the setup actions enforce the repo pin inside the container. Drop the credentials: block only if the shared images are public (see caveats); a personal-repo GITHUB_TOKEN cannot authenticate to liminal-hq packages either way.

  3. Same file, rust-tests:

    Before:

    container:
      image: ghcr.io/scottmorris/liminal-notes-ci-base-desktop:latest
      ...
    steps:
      ...
      - name: Resolve Rust toolchain
        id: rust_toolchain
        shell: bash
        run: |
          set -euo pipefail
          TOOLCHAIN="$(awk -F '\"' '/^[[:space:]]*channel[[:space:]]*=/ { print $2; exit }' rust-toolchain.toml)"
          ...
      - name: Setup Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ steps.rust_toolchain.outputs.toolchain }}

    After:

    container:
      image: ghcr.io/liminal-hq/tauri-ci-desktop:latest
    steps:
      # Resolve/Setup Rust toolchain steps deleted — the image ships 1.96.1,
      # matching rust-toolchain.toml, so rustup needs no download.
  4. In .github/workflows/publish-desktop.yml, publish-linux: replace the container the same way (ghcr.io/liminal-hq/tauri-ci-desktop:latest is multi-arch, so both ubuntu-24.04 and ubuntu-24.04-arm legs keep working) and delete the two toolchain steps. Bonus: the shared image ships the portable-AppImage tauri-cli fork and defaults consistent with TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: "true" (keep the env var — it is harmless and explicit). publish-windows is untouched.

  5. In .github/workflows/build-android.yml, build-android:

    Before:

    container:
      image: ghcr.io/scottmorris/liminal-notes-ci-base-android:latest
      credentials:
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    After:

    container:
      image: ghcr.io/liminal-hq/tauri-ci-mobile:latest

    Verify the EAS/Gradle build against NDK 28.2 first (see caveats) — this is the one mapping with a real version delta (27.1.12297006 → 28.2).

  6. Once all four workflows are green on the shared images, delete:

    • .github/workflows/build-ci-images.yml
    • docker/ci/Dockerfile.ci-base

    and mark the three liminal-notes-ci-base-* GHCR packages deprecated (or delete them after a grace period).

  7. Devcontainer: replace .devcontainer/Dockerfile with the shared dev image in .devcontainer/devcontainer.json:

    Before:

    "dockerFile": "Dockerfile",

    After:

    "image": "ghcr.io/liminal-hq/tauri-dev-mobile:latest",

    then delete .devcontainer/Dockerfile. Everything it built by hand — GUI stack, JDK 17, Android SDK 36/NDK 28.2, Rust, gh, the portable-AppImage tauri-cli fork, TAURI_BUNDLER_NEW_APPIMAGE_FORMAT — ships in tauri-dev-mobile (user vscode, toolchains under $HOME). The audio runArgs/Pulse mounts in devcontainer.json carry over unchanged.

Validation

  • The shared images pull anonymously (this repo is under ScottMorris, outside the liminal-hq org): docker pull ghcr.io/liminal-hq/ci-web:latest, .../tauri-ci-desktop:latest, .../tauri-ci-mobile:latest all succeed without docker login.
  • docker run --rm ghcr.io/liminal-hq/ci-web:latest bash -lc 'node --version && pnpm --version && python3 --version' — confirm python3 availability; the old test image installed it explicitly for node-gyp builds. If absent, add an apt-get install -y python3 step to the JS jobs or request it in the shared base.
  • docker run --rm ghcr.io/liminal-hq/tauri-ci-desktop:latest rustc --version prints 1.96.1; pnpm run ci:test:rust passes on a PR branch with no toolchain download in the log.
  • pnpm run ci:install and all four JS jobs green in ci-web (Node 20.19.6 still selected by setup-node — check node --version output in the job log).
  • Dispatch build-android.yml with the preview profile against a test tag: EAS build completes under NDK 28.2 and the APK/AAB artefacts attach to the draft release.
  • Dispatch publish-desktop.yml on a draft desktop-v* tag: both Linux legs (x64 + arm64) bundle and upload; Windows legs unaffected.
  • After deletion, confirm no workflow references liminal-notes-ci-base- (git grep liminal-notes-ci-base).

Caveats

  • Cross-org image access is the gating question. GITHUB_TOKEN in a ScottMorris repo cannot read liminal-hq private packages. The shared images must be public, or each container: needs a PAT stored as a repo secret in a credentials: block. Verify before starting; everything else depends on it.
  • NDK delta: 27.1.12297006 → 28.2. The Expo/EAS Android build (ci:android:build:*) may pin an NDK via Gradle properties; if it hard-requires 27.x, either update the app's NDK pin or add an sdkmanager --install "ndk;27.1.12297006" step (slow — prefer updating the pin).
  • Node major delta: image ships Node 24; repo engines say >=20.19.6 <21. Keeping setup-node with .node-version in every JS job is mandatory until the repo is ready to move to a newer Node line. Do not run pnpm/node steps before setup-node in those jobs.
  • Rust moves from floating stable (whatever the last local image build captured) to pinned 1.96.1 — deterministic, but check clippy/compiler output once.
  • The old test image's python3 has no guaranteed equivalent in ci-web — validated above.
  • Timing: shared images publish once Split shared CI images into granular tiers matched to org toolchain profiles liminal-hq/.github#22 lands on main; do not merge before docker manifest inspect ghcr.io/liminal-hq/tauri-ci-desktop:latest succeeds. For the release workflow (publish-desktop.yml), consider pinning sha-<commit> tags once stable instead of latest.
  • Windows publish legs and publish-site.yml (Pages) are out of scope.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions