Report: move the Pi image build to GitHub + GHCR

Decision

Yes. Build the image in GitHub Actions with Docker Buildx, publish it to GitHub Container Registry (GHCR), and keep using rootless Podman locally only to pull and run it.

Recommended first target:

Docker-built OCI images are compatible with Podman. A Docker daemon is not required on the workstation.

Current flow

.local/bin/pi.sh currently:

  1. Requires Podman.
  2. Uses the hard-coded image localhost/pi-runner:0.84.4.
  3. Runs setup/pi/build.sh if that image is absent or --build is supplied.
  4. Runs the image with the existing workspace, home, Pi state, AWS, X11, network, and optional Podman-socket mounts.

setup/pi/build.sh is only called by pi.sh; there is no existing GitHub Actions workflow or registry configuration in this checkout.

The runtime contract can remain unchanged. Only image acquisition needs to change.

Minimal implementation

1. Publish from GitHub Actions

Add .github/workflows/pi-image.yml using:

  • actions/checkout
  • docker/login-action for ghcr.io
  • docker/setup-buildx-action
  • docker/metadata-action
  • docker/build-push-action
  • GitHub Actions cache (type=gha)
  • linux/amd64 only
  • packages: write and contents: read job permissions

Build context must be the repository root:

context: .
file: ./setup/pi/Containerfile
platforms: linux/amd64
push: true

Use a commit tag such as sha-<full-commit> as the immutable human-selected tag. Record the pushed digest and ultimately consume the image by @sha256:..., not latest.

A release-only workflow is the safest publication policy. A workflow_dispatch option is useful for rebuilding after dependency or base-image changes. Do not publish from pull_request or use pull_request_target with untrusted checkout content.

2. Fix the build context before the first CI run

There is a current mismatch:

  • setup/pi/Containerfile copies .local/bin/helium-agent.sh.
  • .containerignore excludes .local/bin and does not unignore that file.
  • Docker Buildx normally uses .dockerignore, not .containerignore.

Add a .dockerignore for the GitHub build and update .containerignore for Podman. Both should explicitly allow:

setup/**
.local/lib/**
.local/bin/helium-agent.sh
.pi/agent/extensions/package.json
.pi/agent/extensions/package-lock.json

Keep the allow-list narrow. Do not send the complete home directory, Pi state, credentials, or unrelated dotfiles as build context.

Also add this label to the image metadata or Containerfile:

LABEL org.opencontainers.image.source="https://github.com/PrabhashDiss/dotfiles"

This lets GHCR associate the package with the repository.

3. Change pi.sh from build-on-miss to pull-on-miss

Replace the local build branch with the remote image reference:

IMAGE=ghcr.io/prabhashdiss/dotfiles-pi-runner@sha256:<published-digest>

if ! podman image exists "$IMAGE"; then
    podman pull "$IMAGE"
fi

The existing podman run flags and mounts can stay as they are.

Remove --build, or rename it to an explicit --refresh that performs podman pull. Keeping a local-build path would preserve the unwanted behavior and add a second image source to maintain.

Delete setup/pi/build.sh once no local fallback is desired. Update the comments and README so the wrapper’s pull-only behavior is explicit.

Tagging choices

Choice Benefit Cost
Mutable :0.84.4 tag Smallest script change; easy updates Reproducibility and rollback are weaker
Commit tag :sha-... Easy to identify the source revision The wrapper must be updated for each new image
Digest @sha256:... Strongest reproducibility and rollback The digest must be updated deliberately after publication

Use a commit tag for publication and a digest in the final pi.sh. A mutable :0.84.4 alias may be published for convenience, but should not be the authoritative runtime reference.

The current PI_VERSION=0.84.4 identifies the Pi CLI, not the complete image. Changes to setup/**, the extension lockfiles, helium-agent.sh, or the base image can change the image without changing Pi’s version. Those changes must trigger a rebuild and produce a new image digest.

Authentication

Public package

This is the simplest option: podman pull works without a login. It is reasonable if the image contains only public source and tools, and no credentials are copied into it.

Private package

Authenticate on the host, outside the container, before running pi.sh:

podman login ghcr.io

Do not make pi.sh perform an interactive login and do not pass registry credentials into the container. GitHub documents a classic PAT with read:packages for private command-line pulls. A package linked and published by the repository workflow can use the repository’s GITHUB_TOKEN during publication.

Runtime risks that GHCR does not solve

The image is a trusted-code boundary. The wrapper gives it access to:

The current rootless, read-only-rootfs, dropped-capability setup is useful, but a compromised image can still read user-readable files and use explicitly mounted credentials. Keep --podman opt-in and verify the image digest before trusting it.

The hard-coded Helium amd64 package and other architecture-specific downloads mean that publishing linux/amd64 is the correct minimal scope. Do not advertise multi-architecture support until every dependency and the runtime wrapper have been tested on it.

Rollout plan

  1. Commit the container-context fixes and workflow; do not include local state or ignored credentials.

  2. Run the GitHub build and verify the image digest, entrypoint, architecture, and GHCR package visibility.

  3. Update pi.sh to the verified digest.

  4. On the workstation, remove the old local image if desired:

    podman rmi localhost/pi-runner:0.84.4
    
  5. Run pi.sh once in the normal X11 session and once through the scheduled Pi job path.

  6. Confirm FreshRSS/SearXNG network names, AWS access, Helium, Pi extensions, writable state, and optional nested-Podman behavior.

  7. Keep setup/pi/build.sh deleted unless an emergency offline/local fallback is an explicit requirement.

Conclusion

The clean design is: GitHub Actions builds; GHCR stores; Podman pulls and runs. The smallest complete code change is to replace the build-on-miss branch in .local/bin/pi.sh, add a GHCR workflow, and fix the Docker/Podman build-context allow-list. Digest pinning avoids silently running a changed image and avoids maintaining a local image-building toolchain.

Sources