CI/CD
CI/CD connects a git repository to your cluster: every time you push, Cosmos builds your code on one of your nodes, stores the result in your own registry, and deploys it. Push to main, and a minute later the new version is live. This is the workflow of Vercel, Netlify or Railway, on your own servers.
It can build and deploy three kinds of projects:
- Containers: an application built into a Docker image and run as a deployment, with or without a Dockerfile.
- Static sites: a website served from a static site registry.
- Functions: Cloud Functions published and deployed from your repository.
Before you start
- CI/CD lives in the Constellation section, so Constellation must be enabled. A single server works.
- Create the registry the builds will go to: a Docker registry for containers, a Static site registry for websites, an npm or PyPI registry for functions.
- Your repository must be reachable over
https, on GitHub, GitLab, Gitea / Forgejo, Bitbucket or any other git server. - For pushes to start builds, your git provider must be able to reach your Cosmos server from the internet. Otherwise, you can still start builds by hand.
Connecting a repository
Go to Constellation > CI / Builds and click Connect a repository. A short wizard takes you through it.
1. Repository
- Repository URL: the
httpsaddress of the repository. The Provider is recognised from it. - Access token: a token created on your git provider, that lets Cosmos read the code, install the webhook and report the build results on your commits:
- GitHub: a fine-grained token with Contents: read, Webhooks: write and Commit statuses: write (plus Pull requests: read if you build pull requests from collaborators).
- GitLab: a project access token with the
apiscope. - Gitea / Forgejo: an access token with repository read and write.
- Bitbucket: an app password with Repositories: read and Webhooks: read and write, and your Username.
- Git: optional, the password or token to clone with.
- Project name: suggested from the repository. Lower-case letters, digits and dashes. It cannot be changed later.
In Advanced: the Root directory to build when the repository holds several projects, the Default branch, a Branch filter to only build some branches (main, release/*), and Build node tags to choose which nodes run the builds. For self-hosted GitLab and Gitea, an API URL field is there if the API is not at the same address as the repository.
2. Detection
Cosmos clones the repository and tells you how it is going to build it. It looks, in this order, for:
- a
cosmos.jsonfile, where you describe the build and the deployment yourself (see cosmos.json); - a
.woodpecker.ymlpipeline, which is run as it is; - a
Dockerfile, which is built into an image; - a known language. Cosmos recognises Node, Python, Go, PHP, Ruby, Java, Rust, Deno, .NET, Elixir and more, and builds an image by itself, no Dockerfile needed.
3. Build
Most of the time there is nothing to change here. Build strategy is on Auto-detect; you can force Dockerfile, Railpack (auto-detected language), Static site, Woodpecker pipeline or No build. For a static site, choose the Folder to publish (dist, public...).
You can also set the image Platform (empty means the architecture of the build node), a Timeout (60 minutes by default) and Build-time environment variables. For anything sensitive, use secrets instead.
4. Registry & deploy
- Docker registry: where the images are pushed. Cosmos creates its own token on the registry, and the nodes use it to pull the images: nothing to configure.
- Static-site registry: for static sites.
- Deploy the artifact: on by default. Turn it off to only build.
- Deploy as: Container deployment or Static site.
- Deployment name (or Site name): the deployment is created on the first build. On the next builds, only its image changes, so you stay free to adjust it from the Deployments page (replicas, tags, environment...).
- Replicas, Container port and Hostname: with a hostname and a port, Cosmos creates the URL of the application, load balanced between the replicas and protected by the SmartShield.
- Runtime environment of the created deployment: the environment variables of the application.
- Single-page application, for static sites: unknown addresses fall back to
index.html.
The Environments, Pull-request previews and Pull requests settings are explained below. Their defaults are safe: the default branch is deployed, and pull requests are built without any access to your secrets.
5. Done
Cosmos installs the webhook on your git provider, and from now on every push starts a build. If it could not (plain git server, token without the webhook permission), the last screen gives you the Webhook URL and the Webhook secret to add by hand on the repository, for push and pull-request events, in JSON. You can find them again, retry with Register on the provider, or Rotate the secret, from the Overview tab of the project.
Push a commit, or click Run a build on the project page, and watch your first build.
Following a build
The Builds tab, on the main page for all projects or inside a project, lists the builds with their status, the branch and commit that triggered them, what they produced and where it was deployed. Click a build to open it.
The build page shows the Steps on the left (checkout, your own steps, Build & push or Publish, then Deploy) and the log of the selected step on the right, live while it runs. The first step, prepare, sums up what Cosmos decided: the strategy, the image name, the deployment target. The Result card gives the image that was built (with a link to the registry) and the deployment that was created or updated, with its URL.
A build is Queued, Running, then Passed or Failed. It can also be Canceled, Skipped when the rules of your pipeline exclude the commit, or Awaiting approval (see Pull requests).
The buttons, on the build page and in the list:
- Cancel build: stops a queued or running build. Pushing a new commit on a branch also cancels the builds of that branch that are still waiting.
- Run again: builds the same commit again.
- Deploy this build: deploys again what this build produced, without rebuilding. This is how you roll back: open the last good build and deploy it.
- Approve: lets a pull request waiting for approval run.
The result of each build is also reported on the commit on your git provider, with a link back to the build page. When a build fails, admins get a notification, and a CI Build Failed alert is created for you in the monitoring alerts, where you can add an email or tune it.
Images are named after the registry, the project and the application, and tagged with the commit (docker.mydomain.com/myproject/myapp:sha-1a2b3c4d5e6f), plus a tag that follows the branch (:main).
Branches and environments
By default, only the default branch is deployed. Other branches are built, which is handy to know they still compile, and not deployed.
Environments, in the project settings, decide which branch deploys where. Click Add an environment for each:
- Branch (glob):
main,develop,release/*... - Environment: a name, such as
productionorstaging. - Deployment name and Hostname: where this branch goes. Each environment is its own deployment, with its own URL.
- auto: on by default. Turn it off for an environment you want to deploy by hand: builds are prepared, and go live when you click Deploy this build.
A classic setup: develop deploys automatically to staging.mydomain.com, and main to production with auto turned off.
Pull requests
Previews
Turn on Deploy a preview per open pull request and every pull request gets its own temporary deployment, with its own URL, to review the change for real before merging.
- Preview hostname: a pattern such as
pr-{{pr}}.preview.mydomain.com. Empty, Cosmos prefixes the hostname of the application withpr-and the number of the pull request. - Preview lifetime:
72hby default, counted from the last build of the pull request.
Previews are removed when the pull request is closed or merged, or at the end of their lifetime. The live ones are listed on the Overview tab of the project. Previews are made for container deployments, and need pull requests to run the full pipeline, which brings us to the next setting.
Who can run what
A pull request runs code written by someone else, on your servers, potentially with your secrets. The Pull requests setting decides how much you trust them:
- Build without secrets and without pushing (the default): pull requests are only compiled, to check that they build. Nothing is pushed or deployed.
- Full pipeline for collaborators, without secrets for others: people with write access to the repository get the full pipeline, previews included. Others are only compiled.
- Wait for an administrator's approval: the build waits as Awaiting approval until someone clicks Approve in Cosmos, then runs the full pipeline.
- Do not build pull requests.
Secrets
The Secrets tab of a project holds the sensitive values your builds need: API keys, tokens, passwords. They are stored once and never shown again, and they are hidden from the build logs.
Secrets are available as environment variables in every step of the build. In a Dockerfile, they are mounted the BuildKit way, so they never end up in the image:
RUN --mount=type=secret,id=NPM_TOKEN \
NPM_TOKEN=$(cat /run/secrets/NPM_TOKEN) npm ci
In a .woodpecker.yml, use them with from_secret and the name in lower case.
Each secret has a PR builds switch, off by default: even a trusted pull request only receives the secrets you explicitly allow.
cosmos.json
Everything above can be set from the interface. But you can also commit a cosmos.json file at the root of the repository, and keep the build and the deployment next to the code. When the file exists, it takes over from the settings of the project for what it declares.
A container application
{
"ci": {
"steps": [
{ "name": "test", "image": "node:22-alpine", "commands": ["npm ci", "npm test"] }
]
},
"deploy": {
"name": "myapp",
"replicas": 2,
"compose": {
"services": {
"myapp": {
"container_name": "myapp",
"image": "${artifact}",
"restart": "unless-stopped",
"environment": ["DATABASE_URL=${db.main.app.url}"],
"routes": [
{
"Name": "myapp",
"Mode": "SERVAPP",
"Target": "http://myapp:8080",
"UseHost": true,
"Host": "app.mydomain.com",
"Tunnel": "_ANY_",
"LBMode": "load_based",
"SmartShield": { "Enabled": true }
}
]
}
}
}
},
"environments": {
"main": "production"
}
}
ci.stepsrun before the build, each in the container image of your choice. If a step fails, the build stops there: this is where your tests go.deployis exactly what you would write in the deployment form: name, replicas or autoscaling, tags, and the compose. Everything a deployment can do is available, including template variables.${artifact}is replaced by the image that was just built. Other services of the compose (a Redis, a worker using a public image) are left as they are.environmentsmaps branches to environments, like the Environments table. The long form gives each branch its own target. Add"autoDeploy": falseto an environment you want to deploy by hand:
"environments": {
"main": "production",
"develop": { "name": "staging", "deployment": "myappstaging", "host": "staging.mydomain.com" }
}
A static site
{
"build": { "strategy": "static", "publishDir": "dist" },
"ci": {
"steps": [
{ "name": "build", "image": "node:22-alpine", "commands": ["npm ci", "npm run build"] }
]
},
"deploy": { "type": "static", "site": "blog", "host": "blog.mydomain.com", "spa": false }
}
The steps build the site, then the dist folder is published as a new version of the site blog and goes live. The previous versions stay in the registry, one click away from a rollback.
Functions
{
"build": { "strategy": "none" },
"deploy": {
"type": "function",
"name": "myfunctions",
"function": {
"runtime": "node22",
"source": { "registry": "npm" },
"handlers": [
{ "name": "hello", "handler": "hello", "route": { "Host": "functions.mydomain.com", "PathPrefix": "/hello", "StripPathPrefix": true } },
{ "name": "contact", "handler": "contact", "route": { "Host": "functions.mydomain.com", "PathPrefix": "/contact", "StripPathPrefix": true } }
]
}
}
}
On every push, Cosmos publishes the package of the repository (read from its package.json or pyproject.toml) to the registry, and deploys that exact version to the functions. The function block is the one described in Cloud Functions. For Python packages, remember to bump the version when the code changes.
The build block
build accepts the same settings as the Build step of the wizard: strategy (dockerfile, railpack, static, woodpecker or none), dockerfile, publishDir, platform, timeoutMinutes and env. Leave it out to let Cosmos detect the build. rootDir, at the top of the file, selects a sub-folder.
Using your own pipeline
If the repository already has a .woodpecker.yml, Cosmos runs it as it is, with its steps, services and when rules. You can also call it from cosmos.json with a step of type woodpecker, and keep the deployment in cosmos.json.
Every step, in either format, receives variables describing the build, among which COSMOS_PROJECT, COSMOS_BRANCH, COSMOS_SHA, and for pipelines that push the image themselves, COSMOS_IMAGE, COSMOS_REGISTRY_HOST, COSMOS_REGISTRY_USER and COSMOS_REGISTRY_TOKEN. An image pushed as COSMOS_IMAGE is picked up and deployed like the ones Cosmos builds.
Where builds run
Builds run on the nodes of your cluster. For each build, Cosmos picks the least busy node among those carrying the Build node tags of the project (any node when empty). A node runs up to two builds at a time, and a project builds one commit at a time, so deployments always happen in order.
Building is demanding: on a cluster that also serves traffic, it is a good idea to dedicate a node or two with a build tag. The Build nodes tab shows your nodes, their tags and what they are building.
Cosmos keeps the last 200 builds of each project, with their logs.
Monitoring and events
The Monitoring tab graphs the builds that passed and failed and how long they took, for all projects or for one. The Events tab keeps the history: builds queued, started, passed, failed, approved, deployments and previews. Each deployment made by CI/CD is also recorded in the events of the deployment itself.
Deleting a project
The Danger Zone tab disconnects the repository: the project, its builds and logs are removed, along with its webhook, its registry tokens and its pull-request previews. What was deployed keeps running, and stays yours to manage from the Deployments and Registries pages.