Skip to main content

CI/CD Pipeline

Overview​

GitHub Actions orchestrates linting, type checking, testing, container publishing and the hosted Cloudflare deploys for Inkweld. Successful pushes to main build and push a :dev Docker image to GitHub Container Registry (GHCR) and deploy the preview instance, pushes to production deploy inkweld.app, and version tags (v*) publish the versioned release images, including :latest.

Workflows​

Main CI (.github/workflows/ci.yml)​

CI triggers​

  • Pushes to main
  • Pull requests targeting main

A newer push to the same ref cancels the run in progress.

Jobs​

Most jobs run in parallel on Ubuntu runners. Use bun install --frozen-lockfile from the repo root.

JobWhat it does
OpenAPI Spec + Client FreshnessRegenerates backend/openapi.json and the Angular client (frontend/src/api-client/**) and fails if the committed files differ. Fix with cd backend && bun run generate:openapi && bun run generate:angular-client (needs a Java runtime).
Lintbun run lint (code formatting and lint rules).
Type Checkbun run typecheck: bun check over every TypeScript project (frontend app, specs, e2e and Storybook, backend, shared packages). It is the only check on specs, e2e and the backend.
Unit Tests (Shared Packages)Coverage runs for packages/inkweld-prosemirror, inkweld-presence and inkweld-async.
Unit Tests (Frontend) - Shard 1/1Vitest. One shard; raise shardTotal in ci.yml to split it across runners.
Unit Tests (Frontend) - CoverageMerges the shards' coverage reports (runs even if a shard failed, unless the run was cancelled).
Unit Tests (Backend)Bun test runner with an in-memory database and coverage.
SonarCloud Code AnalysisScans with the merged coverage. Skipped for Renovate dependency PRs, and waits on the sonarcloud-analysis environment.
E2E Tests (Local)Playwright against the frontend in local (offline) mode.
E2E Tests (Online) - 2 shardsPlaywright against a Bun backend.
E2E Tests (Wrangler) - 3 shardsPlaywright against the backend running as a Cloudflare Worker under wrangler dev (local D1).
E2E Tests (Docker) - 2 shardsPlaywright against the Docker image.
E2E Tests (Screenshots)Builds the docs site, which runs the Playwright tests that capture the documentation screenshots.
Build Frontend for DockerPushes to main only. Starts after lint, type check, every unit-test job and every e2e job above have passed (SonarCloud is not required). Builds and compresses the Angular app.
Build Docker (amd64) / Build Docker (arm64)Pushes to main only. Build and push the platform images.
Create Docker ManifestPushes to main only. Combines the two platform images into the multi-arch :dev tag.

Pull requests run everything except the four Docker jobs.

Docker tags published from main​

  • ghcr.io/bobbyquantum/inkweld:dev (multi-arch manifest)
  • ghcr.io/bobbyquantum/inkweld:dev-amd64
  • ghcr.io/bobbyquantum/inkweld:dev-arm64

:latest is not published from main. It only moves when a release is tagged (below) or someone runs the manual publish workflow with the latest option.

Release Workflow (.github/workflows/release.yml)​

Release triggers​

  • A published GitHub release
  • A pushed git tag that matches v*

What it publishes​

Builds the Dockerfile image once and pushes it to GHCR as v1.2.3 (the tag), 1.2.3, 1.2, 1 and latest. It does not re-run the test suite; tag a commit that has already passed CI on main.

Cloudflare deploys (deploy-cloudflare*.yml)​

The hosted instances run on Cloudflare (Pages for the frontend, a Worker + D1 + R2 for the backend). Both are deployed by one reusable workflow, .github/workflows/deploy-cloudflare-reusable.yml, so preview and production build and ship exactly the same way and only differ in their inputs:

PreviewProduction
Caller workflowdeploy-cloudflare.ymldeploy-cloudflare-production.yml
Triggerpush to mainpush to production, or Run workflow on production
GitHub environmentpreviewproduction
Concurrencynewer push cancels a running deployqueued, never cancelled mid-deploy
URLhttps://preview.inkweld.apphttps://inkweld.app
Pages projectinkweld-frontend-previewinkweld-frontend
D1 database / wrangler envinkweld_preview / --env previewinkweld_prod / --env production
Angular configurationpreview (environment.preview.ts)cloudflare (environment.cloudflare.ts)
First visitsetup screen, hosted server pre-filledsetup screen, no server suggested (advertise-server: false)

Each run writes backend/wrangler.toml from the environment's BACKEND_WRANGLER_TOML variable, generates the frontend environment file with frontend/scripts/write-hosted-environment.mjs (the version comes from the root package.json), builds, deploys Pages, applies D1 migrations and deploys the Worker. All variables and secrets are read from the GitHub environment; see Cloudflare installation for the full list.

Branch model​

  • main is the integration branch. Every merge deploys to preview.
  • production is a long-lived branch that only ever points at a commit that is already on main. Pushing to it deploys inkweld.app. Nobody commits to it directly and it is never merged back.

Promoting a release​

Check the commit on preview first, then fast-forward production to it:

git fetch origin
git push origin origin/main:production

To promote an older commit than the tip of main, push that commit instead:

git push origin <commit-sha>:production

The push is rejected unless it is a fast-forward, which keeps production moving forward along main. If the production environment has required reviewers, the deploy waits for an approval in the Actions run. Run workflow on the production workflow redeploys the current production commit (it refuses any other branch).

Hotfixes and rollbacks​

Fix on main like any other change (it reaches preview first), then promote it. To roll back, revert on main and promote the revert; Cloudflare's own deployment rollback (Workers & Pages → Deployments) is the emergency option, but D1 migrations are forward-only, so a rolled-back Worker must still work with the newer schema.

Manual Docker Publish (.github/workflows/docker-publish.yml)​

Use it when you need a hotfix image or want to test a custom tag without merging to main.

  • Triggered manually through the Actions UI
  • Accepts a custom tag (default manual) and a Also tag as latest toggle (off by default)

Other workflows​

WorkflowTriggerPurpose
deploy-pr-preview.ymlPR labelled deploy:preview:frontend, and new pushes to such a PRDeploys the PR's frontend to a Cloudflare Pages branch preview (https://pr-<number>.<pages project>.pages.dev).
cleanup-pr-preview.ymlPR closed, or the deploy:preview:frontend label removedDeletes that PR's Pages deployments.
docs.ymlPush to main touching docs/**, backend/openapi.json; manualBuilds this documentation site and publishes it to GitHub Pages.
validate-pr-title.ymlPR opened, edited, synchronized or reopenedRequires a Conventional Commit title (feat, fix, docs, ci, ...) with a lowercase subject.
deploy-cloudflare.yml, deploy-cloudflare-production.yml, deploy-cloudflare-reusable.ymlPush to main / push to productionHosted deploys, described in the section above.

Image contents​

  • Bun runtime with the Hono API surface
  • Angular production build served via the bun-app.ts static handler
  • LevelDB + SQLite dependencies pre-installed
  • Non-root user with /data as the writable volume

Sample runtime configuration​

SESSION_SECRET, ALLOWED_ORIGINS and WEBAUTHN_RP_ID are the settings a production container needs. See Configuration for the full list.

docker run -p 8333:8333 -v inkweld_data:/data \
-e NODE_ENV=production \
-e SESSION_SECRET=${SESSION_SECRET} \
-e ALLOWED_ORIGINS=https://app.inkweld.org \
-e WEBAUTHN_RP_ID=app.inkweld.org \
ghcr.io/bobbyquantum/inkweld:latest

Build optimizations​

  • Docker layer caching via the actions/cache integration
  • Bun dependency caching for both frontend and backend
  • Frontend build artifacts cached between stages to avoid duplicate work

Monitoring builds​

  • The build status badge in README.md reflects the latest ci.yml run on main
  • Published images are listed on the repository's GHCR package page
  • GitHub Actions logs retain full console output for every job
  • Image metadata embeds the source commit for traceability

Contributor workflow​

  1. Create a feature branch
  2. Make changes + add tests
  3. Push and open a PR
  4. CI runs linting, type checking, unit tests and the e2e suites
  5. Review + merge to main
  6. The Docker jobs publish the :dev image and preview.inkweld.app is redeployed
  7. When preview looks right, promote to inkweld.app by fast-forwarding production

Release workflow​

  1. Create a GitHub release or push a tag (e.g., v1.0.0)
  2. The release workflow pushes the versioned images and moves latest
  3. Clients can pin exact tags (1.0.0, v1.0.0), a minor (1.0), or a floating major (1)

Troubleshooting CI failures​

  • Inspect the failing job logs in GitHub Actions
  • Ensure npm test (frontend), bun test (backend), bun run lint, bun run typecheck and docker build pass locally
  • Verify that secrets (GHCR token, etc.) exist in repository settings
  • Re-run failed jobs from the Actions UI once the issue is fixed