GitHub Actions for provisioning and managing Monk Capsules — fully isolated, prod-like preview environments for every branch.
This repository publishes a suite of composable actions used by the Monk VS Code extension and consumable directly from any GitHub workflow.
| Action | Purpose |
|---|---|
monk-io/monk-actions/provision@v1 |
Provision (or reuse) a Monk cluster for a capsule environment |
monk-io/monk-actions/fetch-metadata@v1 |
Fetch capsule monkcode, JIT tokens, and registry credentials |
monk-io/monk-actions/build-images@v1 |
Build and push all images declared in MANIFEST to a Monk registry |
monk-io/monk-actions/deploy@v1 |
Deploy a workload to a Monk cluster (capsule or static) |
monk-io/monk-actions/cleanup@v1 |
Tear down a capsule on branch close/delete |
provision, fetch-metadata, deploy, and cleanup are Docker container actions bundling the Monk CLI. build-images is a composite action that runs on the host runner so podman/docker can build natively.
build-images and deploy serve two flows:
- Capsule flow (per-branch preview environments): credentials come from
fetch-metadataoutputs (double-base64'd to survive masking across jobs). - Static CI/CD flow (plain deploy to a fixed cluster on push to main): credentials come straight from repo secrets via the plain
monkcode/service-token/registry-passwordinputs, withcapsule-mode: static.
The Monk VS Code extension generates a ready-to-commit .github/workflows/dynenv.yml calling these actions — that is the easiest way to get started.
For hand-written workflows, a minimal example:
jobs:
provision:
runs-on: ubuntu-latest
environment: monk-capsules
steps:
- uses: monk-io/monk-actions/provision@v1
with:
monk-token: ${{ secrets.MONK_CAPSULE_TOKEN }}
environment-name: my-feature-branch
capsule-mode: cloud
cloud-provider: ${{ vars.CLOUD_PROVIDER }}
cloud-region: ${{ vars.CLOUD_REGION }}
cloud-instance-type: ${{ vars.CLOUD_INSTANCE_TYPE }}
cloud-instance-count: ${{ vars.CLOUD_INSTANCE_COUNT }}
cloud-credentials: |
DO_API_TOKEN=${{ secrets.DO_API_TOKEN }}
workload-secrets: |
DATABASE_URL=${{ secrets.DATABASE_URL }}
STRIPE_KEY=${{ secrets.STRIPE_KEY }}
env:
MONK_SUBSCRIPTION_API_BASE: ${{ vars.MONK_SUBSCRIPTION_API_BASE }}
MONK_AUTH_SERVICE_URL: ${{ vars.MONK_AUTH_SERVICE_URL }}
MONK_API_PREFIX: ${{ vars.MONK_API_PREFIX }}
MONK_PROJECT_SLUG: ${{ vars.MONK_PROJECT_SLUG }}
MONK_ORG_SLUG: ${{ vars.MONK_ORG_SLUG }}Routing on a host. A template can take the host of its web service from an ingress-host variable,
so the same template serves a real domain in production or dev and a generated one for each branch:
stack:
defines: group
variables:
ingress-host:
type: string
value: "" # or the real domain, for a deploy that doesn't set itingress-routes:
web:
host: <- $ingress-host
path-prefix: /On a shared cluster (capsule-mode: cluster) every branch deploys to the same cluster, so a web
service's route has to differ per branch. When a template the MANIFEST loads mentions ingress-host, deploy sets it to
<environment>.<domain of the cluster's system node>, and the preview URL is that
host. Everywhere else (a production or dev deploy, a cloud-mode capsule) the variable keeps the value
the template, or the deploy, gives it.
The system node is always there and its domain doesn't change, so a branch keeps its host while the pool grows or shrinks. Every node's ingress serves every route, so the host reaches the workload wherever it runs. If the system node has no domain, any other node's is used. A system node created before the platform answered for subdomains of node domains has no such record, so the host doesn't resolve on an older cluster.
Keep the path-prefix. With the variable empty the route is just PathPrefix(/) and matches every
host, as it did before; a route with only an empty host has no rule at all and is dropped.
A generated name must resolve (the platform answers for any subdomain of a node domain) and, for
https, be covered by the node domain's certificate. Until both are true the preview URL doesn't
answer, which the deploy's preview check reports; with health-check-path set, that fails the run.
The variable is only passed when a template loaded by the MANIFEST (LOAD, or a DIRS sub-MANIFEST) declares ingress-host:, so existing projects are
unaffected until they add it.
Static CI/CD deploy (no capsule environments):
jobs:
build-images:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: monk-io/monk-actions/build-images@v1
with:
registry-address: ${{ secrets.REGISTRY_ADDRESS }}
registry-username: ${{ secrets.REGISTRY_USERNAME }}
registry-password: ${{ secrets.REGISTRY_PASSWORD }}
registry-tls-verify: ${{ secrets.REGISTRY_TLS_VERIFY }}
deploy:
needs: build-images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: monk-io/monk-actions/deploy@v1
with:
monkcode: ${{ secrets.MONKCODE }}
service-token: ${{ secrets.MONK_SERVICE_TOKEN }}
workload: "myapp/stack"
branch-tag: "default"
capsule-mode: staticSee the Monk VS Code extension repo for a complete reference workflow including build, deploy, and cleanup stages.
Fixed secrets (Monk token, cloud provider credentials) are declared as inputs in each action.yml and mapped to env vars via runs.env. This follows the convention used by aws-actions/configure-aws-credentials, docker/login-action, etc.
Workload secrets — whose names depend on the user's manifest — are passed as a single multiline workload-secrets: input, one KEY=value per line. Each value should be a ${{ secrets.X }} interpolation so GitHub masks it independently. This matches the docker/build-push-action secrets: convention.
- Immutable tags:
v1.2.3,v1.2.3-rc.1— pin these in production for reproducibility. - Floating major tag:
v1— moves with each non-pre-releasevX.Y.Zrelease. - The bundled Monk CLI version is pinned via
MONK_CI_VERSIONand bumped automatically when monk releases a newmonk-ciimage.
These actions bundle the proprietary Monk CLI inside the action Docker image. Use of the action is subject to Monk's terms of service. The action source code (scripts, entrypoints, action.yml files) is licensed separately — see LICENSE.