Skip to content

How to deploy from a Quake.yaml manifest with Forgejo Actions

How-to · Updated Sep 2026
Before this

How to deploy from a Quake.yaml manifest with Forgejo Actions

Run a Forgejo Actions or Gitea Actions workflow that turns a push into a deployment through a quake.yaml launch handoff packet. The workflow runs on a forge and runner you operate, then deploys to your Quake AI project. It adapts the generic CI executor for a self-hosted forge.

Prerequisites

Use the self-hosted runner#

A Forgejo Actions runner uses GitHub Actions workflow syntax. Adapt the generic CI workflow for your runner:

  • Place the runner inside or alongside your Quake AI project when the workflow needs access to private addresses.
  • Store deployment credentials in the forge's secret store.

Store secrets in the forge#

In the Forgejo (or Gitea) web UI, open the repository, go to Settings > Actions > Secrets, and add:

SecretPurpose
REGISTRY_USERNAME, REGISTRY_PASSWORDPush credentials for your container registry
OS_AUTH_URL, OS_APPLICATION_CREDENTIAL_ID, OS_APPLICATION_CREDENTIAL_SECRETOpenStack application credentials for the provider
SSH_KEY_NAMEName of the keypair registered in your project
Names listed in manifest secrets[]The application secret values the runtime reads

The workflow reads these values as ${{ secrets.NAME }} so you can keep credentials out of the repository.

Add the workflow#

Commit the workflow to .forgejo/workflows/launch.yml (use .gitea/workflows/launch.yml on Gitea). The runner picks the launch action from the branch: main deploys to production, other branches preview.

YAML
name: Launch from quake.yaml
on:
  push:
    branches: [main]
  pull_request:

jobs:
  launch:
    runs-on: self-hosted
    env:
      REGISTRY: registry.example.com/my-team
      OS_AUTH_URL: ${{ secrets.OS_AUTH_URL }}
      OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }}
      OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }}
    steps:
      - uses: actions/checkout@v4

      - name: Choose the launch action
        run: |
          if [ "${{ github.ref }}" = "refs/heads/main" ]; then
            echo "ACTION=deploy" >> "$GITHUB_ENV"
          else
            echo "ACTION=preview" >> "$GITHUB_ENV"
          fi

      - name: Validate and prepare the handoff packet
        run: |
          yq -o=json '.' quake.yaml > manifest.json
          # Validate first; fail the run on schema or resolution errors.
          jq -n --slurpfile m manifest.json \
            '{jsonrpc:"2.0",id:1,method:"tools/call",
              params:{name:"validate_launch_manifest",arguments:{manifest:$m[0]}}}' \
            > validate.json
          curl -sS https://docs.quake.ai/api/mcp -H 'Content-Type: application/json' -d @validate.json \
            | jq -e '.result.structuredContent.content.valid == true' > /dev/null \
            || { echo "manifest validation failed"; exit 1; }
          # Prepare the packet for the chosen action.
          jq -n --slurpfile m manifest.json --arg action "$ACTION" --arg sha "$GITHUB_SHA" \
            '{jsonrpc:"2.0",id:2,method:"tools/call",
              params:{name:"prepare_launch",
                arguments:{manifest:$m[0],action:$action,source_sha:$sha}}}' \
            > prepare.json
          curl -sS https://docs.quake.ai/api/mcp -H 'Content-Type: application/json' -d @prepare.json \
            | jq '.result.structuredContent.content' > packet.json

      - name: Build and push the image
        run: |
          docker login "$REGISTRY" -u "${{ secrets.REGISTRY_USERNAME }}" -p "${{ secrets.REGISTRY_PASSWORD }}"
          IMAGE="$REGISTRY/$(jq -r '.manifest.name' packet.json):${GITHUB_SHA::12}"
          docker build -t "$IMAGE" -f "$(jq -r '.resolved.build.dockerfile // "Dockerfile"' packet.json)" .
          docker push "$IMAGE"
          echo "IMAGE=$IMAGE" >> "$GITHUB_ENV"

      - name: Apply the runtime template
        run: |
          tofu -chdir=iac/templates/containerized-app init
          tofu -chdir=iac/templates/containerized-app apply -auto-approve \
            -var "flavor_name=$(jq -r '.resolved.flavor' packet.json)" \
            -var "app_name=$(jq -r '.manifest.name' packet.json)" \
            -var "key_name=${{ secrets.SSH_KEY_NAME }}" \
            -var "container_image=$IMAGE"

      - name: Verify
        run: |
          URL="$(tofu -chdir=iac/templates/containerized-app output -raw container_url)"
          curl -fsS "$URL/healthz"

This is the IaC consumer path: it builds the image, then applies resolved.template_plan with OpenTofu. When the manifest declares services[], apply each service entry before the runtime and wire its outputs into the runtime container_env, as the generic CI executor describes.

Use the Coolify consumer instead#

If you run Coolify as your deploy platform, swap the build and apply steps for a single step that runs the Coolify consumer against the packet. The consumer maps the handoff packet to Coolify API calls, so Coolify builds the image, provisions the declared services, and deploys:

YAML
      - name: Deploy through the Coolify consumer
        env:
          COOLIFY_BASE_URL: ${{ secrets.COOLIFY_BASE_URL }}
          COOLIFY_API_TOKEN: ${{ secrets.COOLIFY_API_TOKEN }}
          COOLIFY_PROJECT_UUID: ${{ secrets.COOLIFY_PROJECT_UUID }}
          COOLIFY_SERVER_UUID: ${{ secrets.COOLIFY_SERVER_UUID }}
        run: |
          python consumers/coolify/coolify_launch_consumer.py \
            --packet packet.json \
            --git-repository "${{ github.server_url }}/${{ github.repository }}.git"

The consumer reads the Coolify base URL, API token, project UUID, and server UUID from those environment variables. It takes the git repository URL as a flag. Pick one consumer per repository. The IaC path creates OpenTofu-managed instances. The Coolify path provides a git-push deployment platform. Both consumers read the same packet and use the same manifest.

Verify the result#

The workflow's verify step reads the runtime output and checks the health endpoint. In the forge UI, open Actions, select the run, and confirm each step succeeded. For the IaC path, read the outputs from the runner:

bash
tofu -chdir=iac/templates/containerized-app output -raw container_url

For the Coolify path, open the application in the Coolify dashboard and confirm it shows as running.

Tear down a preview#

Remove a preview environment when its branch merges or closes. For the IaC path, destroy the resources the workflow applied:

bash
tofu -chdir=iac/templates/containerized-app destroy -auto-approve

For the Coolify path, delete the preview application and its managed databases in the Coolify dashboard. The manifest's preview environment can carry a ttl_hours hint; teardown scheduling is your responsibility on a self-hosted forge.

Next steps#

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Last validated: 01.09.2026

Was this page helpful?