K3s deployment lab

Build once.
Remember everywhere.

Aperio’s K3s path turns a commit into an ARM64 container, publishes it to GitHub Container Registry, and gives a small home cluster a signed signal to roll out the new image.

This is a deployment path, not a hosted service. Your cluster, database, secrets, and memories remain under your control. GitHub supplies the build and registry; the Raspberry Pi supplies the runtime.
First decision

What are you trying to prove?

K3s is useful when Aperio should stay running on a small, private machine instead of on a developer laptop.

A private always-on brain

Run the MCP server close to the agents and devices that use it, with the data living on your own cluster.

Home lab
An ARM64 image check

Build the same Docker image that the Pi will pull. Cross-build on GitHub’s runners and catch packaging errors before rollout.

Raspberry Pi
A repeatable release loop

Push to the deployment branch, publish immutable and latest tags, then let the cluster pull and restart the workload.

K3s + Actions
The mental model

A small chain with clear ownership.

Each part has one job. When a deployment fails, start at the last completed hand-off instead of guessing at the whole system.

01
Commit
A push to the deployment branch starts the release workflow, unless the commit opts out with skip ci.
02
Build
GitHub Actions uses QEMU and Docker Buildx to produce an ARM64 image from the repository Dockerfile.
03
Publish
The image is pushed to ghcr.io/<owner>/aperio with both latest and commit-specific tags.
04
Roll out
A protected webhook tells the Pi to pull the image and let K3s restart the deployment.
Why the commit tag matters: latest is convenient for the cluster, while the SHA tag preserves the exact image that was built. When an update misbehaves, you have a precise release to identify and roll back.
The GitHub path

What the deployment workflow does.

The workflow is deliberately boring: checkout, cross-build, publish, notify. The cluster remains the place where the application runs.

01 · CHECKOUT
Use the selected ref

A normal push follows the branch that changed. A manual dispatch can choose a branch, or skip the build when the image already exists.

02 · CROSS-BUILD
Target the Pi deliberately

QEMU and Buildx let an AMD64 GitHub runner produce a linux/arm64 image. The target architecture is explicit, not an accident of the builder.

03 · PUBLISH
Give the cluster a registry

The workflow authenticates to GHCR with the Actions token and pushes the public image under the repository owner’s lowercase namespace.

The important image contractGitHub Actions
platforms: linux/arm64
registry: ghcr.io
tags: latest + ${{ github.sha }}
# optional: notify the Pi after the push succeeds
The trust boundary

Two secrets. One narrow door.

The Pi does not need a GitHub account, and GitHub does not need cluster credentials. The webhook is the small bridge between them.

SecretLives inPurpose
APERIO_PI_WEBHOOK_URLGitHub Actions secretsThe private HTTPS endpoint that receives a deployment notification.
APERIO_PI_WEBHOOK_SECRETGitHub + Pi serviceShared HMAC material so the Pi can verify that the notification is genuine.
GHCR tokenGitHub ActionsThe built-in GITHUB_TOKEN grants package write access for the image push.
Keep the webhook private: use a tailnet or private network when possible, require the HMAC signature, and never put either secret in a manifest, image layer, commit, or browser-facing configuration.
The release loop

From green check to running pod.

A normal release should need no SSH session. Manual dispatch is there for recovery, experimentation, or deploying a branch on purpose.

A
Prepare
Set the two Pi webhook secrets in GitHub and make sure the Pi-side service is enabled.
B
Push
Merge or push to the configured deployment branch. Watch the build and package permissions.
C
Notify
After the image is published, the signed notification asks the Pi to pull and redeploy it.
D
Verify
Check the rollout, then exercise the HTTP and MCP paths that matter to your agents.
Manual recoveryActions → (cd) K3s Deploy
# choose “Run workflow” when you need a deliberate redeploy
branch: master
skip_build: false
# use skip_build only when the desired image is already in GHCR
When something is red

Find the broken hand-off.

01 · ACTIONS
The image was not pushed

Check GHCR permissions, the repository owner casing, and the Buildx/QEMU step. A failed build cannot notify a healthy cluster.

02 · WEBHOOK
The Pi did not react

Check the URL, HMAC secret, HTTPS reachability, and the Pi-side service logs. A successful GitHub job only proves the notification left GitHub.

03 · K3S
The workload will not become ready

Inspect the image architecture, registry pull status, environment configuration, and persistent storage. Keep the previous SHA available until the new pod is healthy.

Remember the boundary: GitHub builds and announces. GHCR stores. The Pi verifies and deploys. K3s runs Aperio. Your cluster owns the memory.