GitHub Actions

A typical workflow: provision infrastructure with formae apply, capture a property from the new resource, deploy an app that needs it, verify.

The whole workflow is itself a GHA::Repo::Workflow resource. One formae apply lays down the workflow file, the variables and secrets it depends on, and the environments it deploys into.

The workflow as a forma

import "@gha/repo/repoworkflow.pkl" as workflow
import "@com.github.actions/Workflow.pkl" as GHAWorkflow

local deployWorkflow = new workflow.Workflow {
    label = "deploy"
    path = ".github/workflows/deploy.yml"
    name = "Deploy"
    on = new GHAWorkflow.On { workflow_dispatch {} }
    permissions = new GHAWorkflow.Permissions {
        `id-token` = "write"
        contents = "read"
    }
    jobs {
        ["provision"] {
            `runs-on` = "ubuntu-latest"
            outputs {
                ["db_host"] = "${{ steps.capture.outputs.db_host }}"
            }
            steps {
                new { uses = "actions/checkout@v4" }
                new { name = "Install formae"; run = "/bin/bash -c \"$(curl -fsSL https://hub.platform.engineering/setup/formae.sh)\"" }
                new {
                    name = "Provision"
                    run = "formae apply --mode reconcile --yes --watch infra/database.pkl"
                }
                new {
                    id = "capture"
                    name = "Capture DB host"
                    run = #"""
                        DB_HOST=$(formae inventory resources \
                          --query='label:pg-server' \
                          --output-consumer=machine \
                          | jq -r '.Resources[0].ReadOnlyProperties.fullyQualifiedDomainName')
                        echo "db_host=$DB_HOST" >> $GITHUB_OUTPUT
                        """#
                }
            }
        }
        ["deploy"] {
            needs { "provision" }
            `runs-on` = "ubuntu-latest"
            steps {
                new {
                    name = "Deploy"
                    run = "deploy_cmd --db-host=${{ needs.provision.outputs.db_host }}"
                }
            }
        }
        ["verify"] {
            needs { "deploy" }
            `runs-on` = "ubuntu-latest"
            steps {
                new { name = "Smoke test"; run = "./run-smoke-tests.sh" }
            }
        }
    }
}

The provision job declares db_host in its outputs, points at a step output via ${{ steps.capture.outputs.db_host }}, and the capture step writes to $GITHUB_OUTPUT. The deploy job consumes via needs.provision.outputs.db_host.

Apply

export GHA_OWNER=my-org
export GHA_REPO=my-repo
formae apply --mode reconcile --watch main.pkl

The workflow file lands in the repo. Trigger it from the Actions tab.

Full working example

infra-to-app provisions Azure PostgreSQL, deploys Miniflux, and verifies feeds end to end. Same pattern, complete Pkl.

What's next