Deploy from Git
Push code, let Pagifier notice, and keep the CI plumbing out of your repository.
Your platform team connects GitHub, GitLab, or your company Git service once. You commit a small control manifest to a protected branch. After that, signed webhooks start work immediately and Pagifier's reconciliation loop catches anything a webhook misses.
Ask for the Git connection IDs you may use, the templates available to your tenant, and a token with pipeline access. Provider tokens, webhook secrets, and the cluster stay on the platform side.
Choose the small template
You do not have to design a pipeline graph for normal applications. Pagifier ships four versioned templates:
| Template | Use it when |
|---|---|
single-repo-service | One repository change should build and deploy the app |
two-repo-service | Application changes build; an operations-repository pin deploys |
build-only | This repository only produces immutable artifacts |
deploy-only | This repository only promotes pinned artifacts |
The two-repository template is the safest production default: merging application code produces an artifact, but changing production still requires an explicit, reviewable pin in the operations repository.
The two-repository flow
application repository operations repository
---------------------- ---------------------
src/** changes
|
v
Pagifier builds an immutable
pagifier:// artifact
release pin changes
|
v
approval, if required
|
v
Pagifier deploys that
exact artifact
No "latest successful build" lookup happens during deploy. The pin names the exact artifact, so a retry tomorrow promotes the same bits.
1. Keep the project file with the app
The version 1 project file describes what actually runs:
version = 1
name = "frontend"
template = "react-static"
environment = "prod"
ingress = "frontend.company.com"
This is the same application configuration used by the upload API. The Git control manifest simply points at it.
2. Render the control manifest
The web console has a Set up Git deployment form, or you can ask the API for a commit-ready file:
curl --fail-with-body \
-H "Authorization: Bearer $PAGIFIER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name":"frontend",
"buildConnection":"git_application",
"buildRepository":"acme/frontend",
"deployConnection":"git_operations",
"deployRepository":"acme/deployments",
"environment":"prod",
"projectFile":"app.pagifier.toml",
"pinFile":"apps/frontend/prod.toml",
"approvalRequired":true
}' \
"$PAGIFIER_URL/v1/pipeline-templates/two-repo-service/render"
Commit the returned pagifier.toml to a protected branch. It looks like
this:
version = 2
name = "frontend"
template = "two-repo-service"
templateVersion = "1"
[repositories.application]
connection = "git_application"
slug = "acme/frontend"
ref = "refs/heads/main"
[repositories.operations]
connection = "git_operations"
slug = "acme/deployments"
ref = "refs/heads/main"
[build]
repository = "application"
projectFile = "app.pagifier.toml"
paths = ["src/**", "package.json", "package-lock.json"]
[deploy]
repository = "operations"
environment = "prod"
pinFile = "apps/frontend/prod.toml"
[environments.prod]
approvalRequired = true
approvalCount = 1
approverRoles = ["approver"]
The paths list keeps README-only changes from waking a builder.
3. Register it once
Registration fetches and validates the manifest, snapshots its commit, and starts watching every repository it names:
curl --fail-with-body \
-H "Authorization: Bearer $PAGIFIER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"connectionId":"git_operations",
"repository":"acme/deployments",
"ref":"refs/heads/main",
"manifestPath":"pagifier.toml"
}' \
"$PAGIFIER_URL/v1/pipelines"
Pagifier initializes its cursors here without doing a surprise first build or deployment. Later control-manifest edits are validated before they become active. A broken edit records an error while the last known good version keeps running.
4. Build, then pin
An application change creates an inactive Release and builds it with rootless BuildKit. Once it is built, the run exposes an immutable reference:
pagifier://artifacts/r20260725-120000-ab12cd34
Put that reference in the operations repository:
version = 1
artifact = "pagifier://artifacts/r20260725-120000-ab12cd34"
Commit the pin. Only that file triggers the deploy stage. If the
environment requires approval, the run pauses at pending_approval
until the required approvers accept it:
curl --fail-with-body \
-H "Authorization: Bearer $PAGIFIER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"decision":"approved","comment":"release review complete"}' \
"$PAGIFIER_URL/v1/pipeline-runs/$RUN_ID/approval"
The token needs the deployment.approve permission and one of the roles
required by the environment policy. Approval never quietly upgrades a
builder or developer token into a deployer.
An image built elsewhere works too, as long as it is pinned by its full digest and the lock points to deployment configuration:
version = 1
artifact = "oci://registry.example.com/acme/frontend@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
projectFile = "apps/frontend/app.pagifier.toml"
Tags such as latest are intentionally rejected. They are convenient
right up until you need to explain what production is actually running.
See what is happening
Use the console, or list runs:
curl -H "Authorization: Bearer $PAGIFIER_TOKEN" \
"$PAGIFIER_URL/v1/pipeline-runs?pipelineId=$PIPELINE_ID"
A submitted run stays running while Kubernetes is building or rolling
out the Release. It becomes succeeded only after the control plane
reports the real build or deployment complete.
If your team still wants CI to own the trigger, that route is not going away—see CI integration.