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

Source: https://docs.quake.ai/docs/automation/how-to/forgejo-actions-launch-deploy
Markdown: https://docs.quake.ai/docs/automation/how-to/forgejo-actions-launch-deploy.md

---

# 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](/docs/automation/how-to/execute-launch-handoff-from-ci) for a self-hosted forge.

<PrerequisiteBlock>

- A self-hosted [Forgejo or Gitea forge with a registered Actions runner](/resources/deployments/deploy-forgejo-git-ci-template).
- A repository on that forge with a valid `quake.yaml` at its root. Review the [launch handoff contract](/resources/ai-assisted-development/launch) and the [`quake.yaml` reference](/resources/ai-assisted-development/quake-yaml).
- A container registry the target instance can pull from, and push credentials.
- An SSH keypair in your Quake AI project and [application credentials](/docs/tools/generate-app-credentials) for the OpenStack provider.
- The runner image (or host) provides `docker`, `tofu`, `jq`, and `yq`.

</PrerequisiteBlock>

## Use the self-hosted runner

A Forgejo Actions runner uses GitHub Actions workflow syntax. Adapt the [generic CI workflow](/docs/automation/how-to/execute-launch-handoff-from-ci) 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:

| Secret | Purpose |
| --- | --- |
| `REGISTRY_USERNAME`, `REGISTRY_PASSWORD` | Push credentials for your container registry |
| `OS_AUTH_URL`, `OS_APPLICATION_CREDENTIAL_ID`, `OS_APPLICATION_CREDENTIAL_SECRET` | OpenStack [application credentials](/docs/tools/generate-app-credentials) for the provider |
| `SSH_KEY_NAME` | Name 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](/docs/automation/how-to/execute-launch-handoff-from-ci#manifests-that-declare-backing-services) describes.

## Use the Coolify consumer instead

If you run [Coolify](/resources/deployments/deploy-quake-yaml-to-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

- [Execute a launch handoff packet from CI](/docs/automation/how-to/execute-launch-handoff-from-ci): the generic GitHub Actions and GitLab CI version this page specializes
- [Deploy an app to Coolify from a quake.yaml manifest](/resources/deployments/deploy-quake-yaml-to-coolify): the full Coolify consumer walkthrough
- [Deploy a self-hosted git forge and CI with OpenTofu](/resources/deployments/deploy-forgejo-git-ci-template): provision the forge and runner this page runs on

## See also

- [Launch handoff](/resources/ai-assisted-development/launch): the manifest, the packet, and the branch workflow
- [`quake.yaml` reference](/resources/ai-assisted-development/quake-yaml): the manifest fields and resolution rules
- [CI/CD pipelines](/resources/solutions/cicd-pipelines): the broader build, test, and ship pattern on Quake AI
