Test and Deploy Django With Gitea or Forgejo Actions
Use Gitea Actions or Forgejo Actions, GitHub Actions-compatible CI built into self-hosted forges, for Django: register act_runner, write workflows with a PostgreSQL service, push images to the built-in container registry, and deploy over SSH with secrets.
Problem statement
Your code lives on a self-hosted Gitea or Forgejo instance, and you want CI/CD without adding a separate CI server. Both forges include an Actions feature that reads workflow files in GitHub Actions syntax and runs them on act_runner (Forgejo: forgejo-runner) agents you host. Most Django workflows written for GitHub run with small changes, but runners, action sources, services, and secrets need setting up.
- Enable Actions in the forge's configuration and in the repository settings.
- Install
act_runner(orforgejo-runner) on a host with Docker and register it with a token from the admin or repository settings. - Put workflows in
.gitea/workflows/(or.forgejo/workflows/), using GitHub syntax withservices: postgres. - Push images to the forge's built-in container registry with a token stored as a secret.
- Deploy over SSH from a job limited to
main, with the SSH key and known hosts as secrets.
Step-by-step solution
1. Enable Actions and register a runner
In Gitea's app.ini:
[actions]
ENABLED = true
Then on a runner host:
./act_runner register --instance https://git.example.com --token <registration-token> --name runner-1 \
--labels ubuntu-latest:docker://node:20-bookworm
./act_runner daemon
Labels map runs-on values to container images. Run the runner as a service and give it access to Docker. Keep runners for public repositories separate from those that deploy production.
2. Write the workflow
# .gitea/workflows/ci.yaml
name: ci
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
container: python:3.12-slim
services:
postgres:
image: postgres:17
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: shop
env:
DATABASE_URL: postgres://postgres:postgres@postgres:5432/shop
DJANGO_SETTINGS_MODULE: config.settings.test
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- run: python manage.py test --noinput
When the job runs in a container, services are reachable by their names (postgres). Actions referenced with uses: are fetched from GitHub by default (configurable with DEFAULT_ACTIONS_URL), or from your own mirror.
3. Build and push to the built-in registry
build:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: git.example.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- uses: docker/build-push-action@v6
with:
push: true
tags: git.example.com/acme/shop:${{ github.sha }}
Building images inside job containers needs Docker access on the runner (socket mount or Docker-in-Docker). Configure that in the runner's config file deliberately; mounting the socket gives jobs control of the host's Docker.
4. Deploy over SSH
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
echo "${{ secrets.DEPLOY_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
ssh deploy@app.example.com "/srv/shop/bin/deploy ${{ github.sha }}"
The server-side script pulls the new image, runs migrations, and restarts services.
5. Secrets and runner security
Store secrets at the repository, organization, or instance level. Runners execute arbitrary workflow code; do not run untrusted pull request workflows on runners that hold deploy access or Docker socket access. Use separate runner labels for trusted deploy jobs.
Explanation
Gitea and Forgejo Actions implement the GitHub Actions workflow format and delegate execution to runners based on nektos/act. Each job runs in a container chosen by the runner label, with service containers on a shared network. Compatibility is high for typical CI tasks such as checkout, setup, tests, Docker builds, and SSH deploys, while some GitHub-specific features, such as hosted runners and native cloud OIDC, are limited or absent.
Edge cases / notes
- Caching.
actions/cacheworks if the runner's cache server is configured; otherwise dependencies install fresh each run. - Action availability. Some marketplace actions assume GitHub APIs; prefer plain shell steps for critical deploy logic.
- Forgejo differences. Forgejo Actions is closely related but evolving independently; check its documentation for runner and syntax details.
Next steps
- Compare with Test and Deploy Django With Woodpecker CI.
- See the GitHub version in Deploy Django With GitHub Actions CI/CD.
- Keep host verification on with Fix Host Key Verification Failed in CI.
FAQ
Can I reuse my GitHub Actions workflows on Gitea?
Mostly, yes. Move them to .gitea/workflows/ and check actions that depend on GitHub APIs.
Do I need my own runners?
Yes. Gitea and Forgejo Actions run only on runners you register.
Where should images be stored?
The forge's built-in container registry works well and keeps images next to the code.
Is it safe to run pull requests from strangers?
Not on runners with deploy secrets or Docker socket access. Use separate, isolated runners.