Platform CI
This repository is the central, public source for Nuvisphere's Gitea scoped workflows and the shared QuickStack OCI action.
Workflow code contains no credentials. Each consuming organization supplies its own REGISTRY_USERNAME, REGISTRY_TOKEN and QUICKSTACK_API_TOKEN Actions secrets. Organization-owned Platform-CI repositories only provide the scoped workflow entrypoint and call this central action.
Repositories opt into deployment by committing .quickstack/deploy.json. Version 1 manifests continue to build and deploy every target independently. Version 2 separates immutable build artifacts from QuickStack applications, so one artifact can be deployed to multiple processes and a Production pipeline can promote the exact tested Staging digest without rebuilding. A failed rollout or postflight automatically restores the previous QuickStack configuration.
Example:
{
"version": 1,
"registry": "gitea.nuvisphere.de",
"deployments": [
{
"name": "production",
"branch": "main",
"targets": [
{
"name": "website",
"image": "nuvisphere/example",
"dockerfile": "Dockerfile",
"context": ".",
"appId": "app-example-12345678",
"healthCheckTcpPort": 3000,
"postflight": {
"url": "https://example.com"
}
}
]
}
]
}
Pull requests build all matching targets without logging in, publishing or deploying. Pushes and manual runs publish and deploy. The commit marker [skip quickstack-deploy] skips a push deliberately.
Candidate and promotion pipelines
Use manifest version 2 when one image serves multiple applications or Production must promote a tested candidate:
{
"version": 2,
"registry": "gitea.nuvisphere.de",
"artifacts": [
{
"name": "web",
"image": "example/web",
"dockerfile": "Dockerfile",
"buildArgs": { "BUILD_SHA": "$sha12" },
"buildSecrets": {
"framework-build-key": "FRAMEWORK_BUILD_KEY"
},
"requiredFiles": ["/app/server.js"]
}
],
"pipelines": [
{
"name": "staging",
"branch": "staging",
"strategy": "candidate",
"applications": [
{ "name": "web", "artifact": "web", "appId": "app-staging" },
{
"name": "worker",
"artifact": "web",
"appId": "app-worker-staging",
"dependsOn": ["web"],
"environmentFromAppId": "app-staging",
"environment": { "PROCESS": "worker" }
}
]
},
{
"name": "production",
"branch": "prod",
"strategy": "promote",
"source": { "branch": "staging", "mergeParent": 2, "requireTreeMatch": true },
"release": {
"versionFile": "content/releases/latest.json",
"notesFile": "content/releases/$version.md",
"skipMarker": "[skip prod-release]"
},
"applications": [
{ "name": "web", "artifact": "web", "appId": "app-production" }
]
}
]
}
buildSecrets is optional and maps a BuildKit secret mount ID to the name of
an Actions secret exposed to the workflow environment. Values are passed to
docker build through --secret id=...,env=...; they are never included in
the command line as build arguments. The Dockerfile consumes them with
RUN --mount=type=secret,id=<id>,required=true ....
environmentFromAppId is optional and copies the existing QuickStack runtime
environment from another application before applying the target application's
declared environment and secretEnvironment overrides. This is intended for
workers that share backend credentials with an API without duplicating secret
values in Git or Actions. volumeMountPaths can map an existing QuickStack
volume ID or shared-volume ID to a safe absolute container path; it never
creates or replaces a volume. QuickStack's deployment identity variable is
never inherited from the source application.
Candidate pipelines build every artifact once, push sha-<commit>, resolve the registry digest and deploy applications in dependency order. Same-repository pull requests publish their verified immutable candidates immediately. A fast-forward merge keeps the tested commit SHA, so the subsequent branch push only pulls, verifies and deploys those candidates instead of building them again. A no-ff merge commit is also reused when its tree matches the configured merge parent, so Gitea's merge commit does not force a second identical build. Fork pull requests remain validation-only and never publish. A pull request into a promotion branch must originate from source.branch; it pulls and verifies the already tested sha-<commit> candidates without rebuilding or deploying them. Promotion pushes require a merge parent with an identical Git tree, trace tree-equivalent staging merge commits back to their tested candidate parent, pull the existing candidate, verify required container files, add the SemVer alias, deploy the exact digests, create the immutable tag and publish the canonical Gitea release. Tag creation does not trigger another scoped pipeline because the workflow listens only to branch pushes.
Scoped workflows can distribute version 2 artifact builds across independent
runner jobs by invoking the action with mode: build and a zero-based
artifact-index. A final job invokes mode: coordinate after all build jobs.
Candidate tags are the synchronization boundary, so exact digests never depend
on matrix output merging. Published candidates also maintain a per-image
buildcache tag with inline BuildKit metadata for ephemeral runners. Version 1
manifests remain on the serial coordinator compatibility path.