Reusable GitHub Actions workflows for the DaVinciBot SvelteKit applications.
Application repositories should call reusable workflows by release tag:
jobs:
ci:
uses: DaVinciBot/shared-workflows/.github/workflows/ci.yml@v7.0.1Available workflows:
.github/workflows/ci.yml: shared quality gates. By default runspnpm check,pnpm lint,pnpm test:unit,pnpm buildat the repository root. Inputs:pnpm_version,node_version_file,working_directory(directory the gate commands run in — dependencies are still installed from the repo root so pnpm workspaces link correctly),commands(JSON array of{name, command}to run only the scripts a package defines; overrides the default gates). Theworking_directory/commandsinputs let a monorepo call the workflow once per package (see DaVinciBot/packages)..github/workflows/container.yml: build and publish application containers. Nothing is published before the gates pass:hadolintruns on the Dockerfile, the image is then built withload(neverpush) and inspected locally bydive --ci,dockleand Trivy — same path on a pull request and on a branch push. Only then, and only whenpushis true, a second build publishes to GHCR: every layer is served from the cache of the audited build, so the published image is the one that was inspected, and it carries the provenance and SBOM attestations the local build cannot export. Inputs pin each tool and point at its config:hadolint_version/hadolint_config,dive_version/dive_config,dockle_version/dockle_exit_level,trivy_severity,buildx_cache_dir. Layer caching is local to the runner (type=localunder/opt/buildx-cache/<owner>-<repo>,mode=max), nottype=gha: on a self-hosted runner the layers stay on the box instead of round-tripping to GitHub's cache service..github/workflows/deploy.yml: deploy applications through Dokploy..github/workflows/e2e.yml: run Playwright end-to-end tests. Inputplaywright_install_deps(defaultfalse) controlsplaywright install --with-deps; it is off because the runner shares its host with production and--with-depsruns apt under sudo on that host at every run. Install the Playwright system libraries once on the host instead — the browsers themselves persist in the runner cache between runs..github/workflows/security-scan.yml: run security scans — dependency review, Trivy on the filesystem, andcheckovon the Dockerfile and the workflows. Inputs:checkov_version,checkov_config..github/workflows/publish-npm.yml: publish an npm package to GitHub Packages (npm.pkg.github.com). Idempotent (skips already-published versions). Inputs:package_dir,build_command,pnpm_version,node_version_file. No npm provenance: attestation is npmjs-only..github/workflows/release-changesets.yml: open and keep up to date the "Version Packages" PR from the pending changesets. It does not publish anything — merging that PR is what triggerspublish-npm.ymlin the calling repository. Inputs:pnpm_version,node_version_file,version_script,pr_title,commit_message. The caller must grantcontents: writeandpull-requests: write, since a reusable workflow can only narrow the caller's token.
Each application repository carries the container tooling configs at its root. They are read from the checkout, so a repository tunes its own thresholds without touching this repository:
| File | Tool | Holds |
|---|---|---|
.hadolint.yaml |
hadolint | failure threshold, ignored rules, trusted registries |
.dive-ci |
dive | efficiency, wasted bytes and wasted-percent thresholds |
.dockleignore |
dockle | waived CIS checkpoints |
.checkov.yaml |
checkov | frameworks, skipped paths, waived checks |
Required repository or organization setup:
- Allow application repositories to use reusable workflows from
DaVinciBot/shared-workflows. - Create and maintain version tags such as
v7.0.1after changes are reviewed. - Grant GitHub Actions
packages: writefor workflows that publish to GHCR. - Grant GitHub Actions
id-token: writefor workflows that create keyless Cosign and npm signatures. - Configure deployment environments
dev,staging, andprodin application repositories, with a required reviewer onprod. - Configure repository secrets (shared across environments):
DOKPLOY_URLDOKPLOY_API_KEYGHCR_TOKEN(PAT withread:packages, used by Dokploy to pull from GHCR)
- Configure the organization secret
PACKAGES_READ_TOKEN(PAT withread:packagesfrom a bot account): used byci.yml/e2e.ymlinstalls and mounted as a Docker build secret bycontainer.ymlso builds can install the private@davincibot/*packages from GitHub Packages.container.ymltreats it as required. - Configure environment secrets (one per environment:
dev,staging,prod):DOKPLOY_APP_ID
Every job runs on runs-on: [self-hosted, helsinki] — a single persistent organisation runner, co-located with
production, so one job runs at a time and the whole file set is written for that. Consequences worth keeping in mind:
- The runner is not a GitHub-hosted image. Node and pnpm are set up explicitly by the workflows that need them; do not add a step that assumes the toolchain a GitHub-hosted image would have preinstalled.
/opt/buildx-cacheis not touched by the host's dailydocker system prune.container.ymlkeeps it bounded by writing each build to<dir>-newand swapping it in, so a repository holds one generation of layers rather than accumulating all of them. If the directory still grows past what the box can spare, move that repository to a registry cache (type=registryagainst a:buildcachetag on GHCR) rather than adding a prune step here.- The per-repository subdirectory exists because a second runner on the same host writing the same local cache concurrently can corrupt it. Adding a runner means either keeping one cache root per runner or moving to a registry cache.
.github/actionlint.yamldeclares thehelsinkilabel; without it actionlint rejects everyruns-on.
The shared npm packages live in DaVinciBot/packages:
@davincibot/config, @davincibot/lib and @davincibot/components, published to GitHub Packages (private) via
publish-npm.yml. @davincibot/database-types is published the same way
from DaVinciBot/Supabased.
The first-generation packages (@davincibot/eslint-config,
@davincibot/prettier-config, @davincibot/tsconfig, on public npmjs) are frozen and deprecated in favour of
@davincibot/config v2+.