Release pipeline
How a Skriuw version becomes a pullable Docker image, what the GHCR publish workflow automates, what stays a manual tag, tag conventions, and the one-time public-package setup.
How a Skriuw version becomes a pullable Docker image. This is the maintainer view of the Self-host with Docker story: what CI does, what stays manual, and the one-time setup that makes the image public.
What is automatic
The image publish is CI. .github/workflows/publish-docker.yml triggers on any
v*.*.* tag push (and on manual workflow_dispatch), then:
- Sets up QEMU + Buildx for multi-arch.
- Logs in to GHCR with the built-in
GITHUB_TOKEN. - Builds the root
Dockerfileforlinux/amd64andlinux/arm64. - Pushes to
ghcr.io/remcostoeten/skriuwwith tags derived from the git tag.
For tag v0.22.1 that publishes three tags: :0.22.1, :0.22, and :latest.
Every subsequent vX.Y.Z tag re-publishes and moves :latest forward. GitHub
Actions layer caching (type=gha) keeps rebuilds fast.
What stays manual
Cutting the tag. There is no semantic-release or changesets, versioning is
manual, matching the desktop flow (desktop-v* tags). To ship a release:
# bump "version" in package.json first, then:
git tag v0.22.1
git push origin v0.22.1That single push is the whole release action; the workflow does the rest.
Tag conventions
| Prefix | Triggers | Produces |
|---|---|---|
v*.*.* | publish-docker.yml | The self-host web image on GHCR |
desktop-v* | release-desktop.yml | Native desktop installers + a GitHub release |
The two prefixes fire different pipelines: a v* tag does not build the desktop
installers, and a desktop-v* tag does not build the Docker image. Since
0.22.1 the number is unified: web, desktop, Tauri, and Cargo packages all
carry one version, so a full release means cutting both tags at the same
commit:
git tag v0.22.1 && git tag desktop-v0.22.1
git push origin v0.22.1 desktop-v0.22.1Letting them drift is how desktop-v0.22.0 came to be cut from a branch that
never landed on daddy, publishing a desktop feature the mainline did not have.
Publishing the install channels
release-desktop.yml only drafts the GitHub release. Publishing that draft is
what triggers publish-linux-repos.yml, which fans the artifacts out to apt,
dnf, AUR, Homebrew, Scoop, winget, and Snap. A draft that is never published
ships nothing to any channel.
The DevTUI (scripts/devtui) is cross-compiled in the same workflow and its
skriuw-dev binaries are attached to the desktop release as assets.
One-time setup: make the package public
After the first successful publish, the GHCR package defaults to private. Flip
it to public (repo → Packages → skriuw → Package settings → Change visibility
→ Public). Otherwise docker pull demands a docker login ghcr.io, which
breaks the self-host quick start.
End-user update model
Publishing :latest does not push onto anyone's running container. Self-hosters
upgrade deliberately with docker compose pull && docker compose up -d; the
entrypoint applies migrations on the next boot. Auto-update (Watchtower) is
intentionally left out of the default Compose because unattended migrations have
no rollback.
Collaboration setup
Deploy the Cloudflare Worker that powers realtime collaboration and wire it to the app with a shared HMAC secret and the WebSocket host.
Architecture
The workspace-backend adapter contract, its three implementations (server, local, tauri), the capability system, and the web-spa split that lets the same UI run on web, guest, and desktop.