# Deploy a self-hosted git forge and CI with OpenTofu

Source: https://docs.quake.ai/resources/deployments/deploy-forgejo-git-ci-template
Markdown: https://docs.quake.ai/resources/deployments/deploy-forgejo-git-ci-template.md
> Stand up Forgejo with a bundled Forgejo Actions runner on one CPU VM using the forgejo-git-ci OpenTofu template.

---

# Deploy a self-hosted git forge and CI with OpenTofu

Stand up [Forgejo](https://forgejo.org) with a bundled Forgejo Actions runner on one CPU VM using the [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) `forgejo-git-ci`. Forgejo Actions runs CI from the same workflow syntax as GitHub Actions. A data volume at `/var/lib/forgejo` holds repositories, CI artifacts, and the runner's Docker layers.

<PricingCompanion
  components={[
    { kind: "template", slug: "forgejo-git-ci", required: true },
  ]}
/>

<Figure size="md" caption="Forgejo git and CI topology: private subnet, forge and Actions runner with a data volume, and one floating IP">

```d2
direction: right

cloud: Quake AI {
  fip: Floating IP\nweb + git
  private: Private network\n10.40.0.0/24 {
    forge: Forgejo forge\n:80
    runner: Actions runner\n(docker label)
    vol: Data volume\n/var/lib/forgejo {shape: cylinder}
    forge -> runner: dispatch job
    forge -> vol: repos + artifacts
  }
  router: Router\nto PublicStatic
}

cloud.fip -> cloud.private.forge
cloud.router -> cloud.private
```

</Figure>

## Prerequisites

You need:

- A Quake AI account with [application credentials](/docs/tools/generate-app-credentials)
- OpenTofu 1.6.0 or later ([installation guide](https://opentofu.org/docs/intro/install/))
- OpenStack credentials sourced into the shell (`source openrc.sh`). See [the OpenStack CLI guide](/docs/tools/openstack-cli).
- An existing SSH key pair in your project. See [Add an SSH key](/docs/tools/add-ssh-key).
- `git` installed on your workstation
- A copy of the `forgejo-git-ci` template from [the template reference page](/resources/iac-templates/forgejo-git-ci)
- Enough project quota for one `s1a.medium` instance, a 30 GB boot volume, a 40 GB data volume, and one floating IP

## Step 1: Configure variables

Copy `terraform.tfvars.example` to `terraform.tfvars` and set:

```hcl
key_name = "YOUR_KEY_NAME"
# admin_username = "forgejo-admin"
# admin_email    = "admin@example.com"
# domain         = "git.example.com"
```

Leave `domain` commented out to start on the floating IP over HTTP. Set `domain` later when you point DNS at the forge and configure TLS. Defaults match the [Forgejo Git and CI](/resources/iac-templates/forgejo-git-ci) reference page.

## Step 2: Apply the template

From the template directory, run:

```bash
tofu init
tofu plan
tofu apply
```

Type `yes` when prompted. Cloud-init installs Docker, starts the forge and runner, creates the admin account, and registers the runner.

When the run finishes, record the outputs:

```bash
WEB_URL=$(tofu output -raw web_url)
FORGE_IP=$(tofu output -raw floating_ip)
echo "$WEB_URL"
```

## Step 3: Retrieve the admin password and sign in

cloud-init takes a few minutes after `apply` returns. The admin password is written to `/root/forgejo-credentials` on the instance:

```bash
ssh -i YOUR_PRIVATE_KEY_PATH -o StrictHostKeyChecking=accept-new ubuntu@"$FORGE_IP" 'cloud-init status --wait'
ssh -i YOUR_PRIVATE_KEY_PATH ubuntu@"$FORGE_IP" sudo cat /root/forgejo-credentials
```

Open `web_url` in your browser and sign in with the admin username and generated password. Rotate the password under **Settings** > **Account**, then delete the credentials file:

```bash
ssh -i YOUR_PRIVATE_KEY_PATH ubuntu@"$FORGE_IP" sudo rm /root/forgejo-credentials
```

## Step 4: Push a repository with a CI workflow

Create a private repository named `ci-demo` in the forge UI. Generate a personal access token with the `write:repository` scope under **Settings** > **Applications**.

On your workstation:

```bash
git clone "http://$FORGE_IP/forgejo-admin/ci-demo.git"
cd ci-demo
mkdir -p .forgejo/workflows
cat > .forgejo/workflows/ci.yml <<'YAML'
on: [push]

jobs:
  build:
    runs-on: docker
    steps:
      - uses: actions/checkout@v4
      - run: node --version
      - run: echo "CI is running on my own forge"
YAML
git add .forgejo/workflows/ci.yml
git commit -m "Add CI workflow"
git push origin main
```

Open the **Actions** tab in the `ci-demo` repository and confirm the run goes green. If the run stays queued, check **Site Administration** > **Actions** > **Runners** or read runner logs on the instance.



Add your public key under **Settings** > **SSH / GPG Keys**, then clone with the shape from `git_ssh_clone_example` output: `ssh://git@FORGE_IP:2222/forgejo-admin/ci-demo.git`.



## Step 5: Put a domain and TLS in front (optional)

Set `domain` in `terraform.tfvars`, point the DNS A record at `floating_ip`, run `tofu apply` again, and terminate TLS with Forgejo ACME or a reverse proxy. See the [template reference page](/resources/iac-templates/forgejo-git-ci) for both paths.

## Next steps

- [Forgejo Git and CI template](/resources/iac-templates/forgejo-git-ci)
- [CI/CD pipelines](/resources/solutions/cicd-pipelines)
- [DevOps automation](/resources/solutions/devops-automation)

## Clean up

Run `tofu destroy` from the project directory when finished. Type `yes` to confirm. Verify in the Console that the instance, data volume, and floating IP are gone.
