Files
openrabbit/docs/DEPLOY.md
latte 8cadb2d216
Some checks failed
Docker / docker (push) Successful in 6s
Security / security (push) Successful in 6s
Deploy / deploy-local-runner (push) Has been cancelled
CI / ci (push) Successful in 1m42s
Deploy / deploy-ssh (push) Successful in 7s
Add Gitea Actions workflows, CI config, and docs
2026-02-28 20:40:14 +01:00

9.1 KiB

Deploy Workflow — ${REPO_NAME}

Overview

The deploy workflow (.gitea/workflows/deploy.yml) automates deployment to a VPS after a push to the default branch. It supports two modes and three deployment strategies.

Disabled by default. Set ENABLE_DEPLOY=true in .ci/config.env to enable.

Deployment Modes

The deploy job runs directly on the VPS via a self-hosted act_runner. No SSH keys or network credentials needed — the runner already has local access.

┌──────────┐     push      ┌──────────┐    runs-on: deploy-ovh    ┌───────────┐
│ Developer │ ────────────► │  Gitea   │ ────────────────────────► │ VPS       │
│           │               │  Server  │                           │ act_runner│
└──────────┘               └──────────┘                           │ (local)   │
                                                                   └───────────┘

Pros:

  • No SSH secrets to manage
  • No network exposure (runner is already on the VPS)
  • Simpler setup, fewer moving parts
  • Runner can access local Docker socket, systemd, files directly

Cons:

  • Requires installing act_runner on the VPS
  • One runner per VPS (or one runner with multiple labels)

Mode B: SSH (Fallback)

The deploy job runs on any runner (e.g., shared ubuntu-latest) and SSHs into the VPS to execute deploy commands remotely.

┌──────────┐     push      ┌──────────┐    runs job     ┌────────┐    SSH     ┌─────┐
│ Developer │ ────────────► │  Gitea   │ ──────────────► │ Runner │ ────────► │ VPS │
└──────────┘               └──────────┘                 │(shared)│           └─────┘
                                                         └────────┘

Pros:

  • Works without installing anything on the VPS
  • Can deploy to VPSes you don't fully control

Cons:

  • Requires SSH secrets (private key, host, user)
  • VPS SSH port must be reachable from the runner
  • More moving parts, more to go wrong

Deploy Strategies

Regardless of mode, the workflow supports three strategies for what actually happens on the VPS:

compose (default)

cd /opt/myapp
docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d

Best for: Docker Compose-based projects. Pulls latest images and recreates containers with zero-downtime (depends on your compose config).

systemd

sudo systemctl restart my-service

Best for: Applications managed as systemd services (e.g., a Go binary, Python app with gunicorn, etc.).

script

./scripts/deploy.sh /opt/myapp

Best for: Custom deploy logic that doesn't fit compose or systemd. The script receives DEPLOY_WORKDIR as $1.

Setup Guide

Local Runner Mode

Step 1: Install act_runner on the VPS

# Download the latest act_runner binary
wget https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64
chmod +x act_runner-linux-amd64
sudo mv act_runner-linux-amd64 /usr/local/bin/act_runner

Step 2: Register the runner with your label

# Register with Gitea (get the token from: Site Admin → Runners → Create)
act_runner register \
  --instance https://git.hiddenden.cafe \
  --token YOUR_RUNNER_TOKEN \
  --labels deploy-ovh

# Or for repo-level: Repository Settings → Actions → Runners → Create

Step 3: Configure runner labels

The runner config file (usually ~/.config/act_runner/config.yaml or next to the binary) contains the labels:

runner:
  labels:
    - "deploy-ovh:host"

The :host suffix means the job runs directly on the host (not in a container). This is important for accessing Docker, systemd, and local files.

After changing labels, restart the runner:

sudo systemctl restart act_runner

Step 4: Update workflow runs-on

In .gitea/workflows/deploy.yml, the deploy-local-runner job has:

runs-on: deploy-ovh

If you changed DEPLOY_RUNNER_LABEL in config.env, you must also update this runs-on value in the workflow file to match. Gitea Actions does not support dynamic runs-on from environment variables.

Step 5: Enable deploy

In .ci/config.env:

ENABLE_DEPLOY=true
DEPLOY_MODE=local-runner
DEPLOY_RUNNER_LABEL=deploy-ovh
DEPLOY_WORKDIR=/opt/myapp
DEPLOY_STRATEGY=compose

Step 6: Verify VPS prerequisites

For compose strategy:

# Docker and compose plugin must be installed
docker --version
docker compose version

# The deploy workdir must exist with a docker-compose.yml
ls /opt/myapp/docker-compose.yml

For systemd strategy:

# The service must exist
systemctl cat my-service

For script strategy:

# The script must be in the repo and executable

SSH Mode

Step 1: Create an SSH key

ssh-keygen -t ed25519 -C "deploy@gitea" -f deploy_key -N ""

Step 2: Add the public key to the VPS

ssh-copy-id -i deploy_key.pub user@your-vps

Step 3: Add secrets to Gitea

Go to Repository Settings → Actions → Secrets and add:

Secret Value
DEPLOY_SSH_KEY Contents of deploy_key (private key)
DEPLOY_HOST VPS IP or hostname
DEPLOY_USER SSH username
DEPLOY_KNOWN_HOSTS Output of ssh-keyscan your-vps (recommended)

Step 4: Enable deploy

In .ci/config.env:

ENABLE_DEPLOY=true
DEPLOY_MODE=ssh
DEPLOY_WORKDIR=/opt/myapp
DEPLOY_STRATEGY=compose

Configuration Reference

Variable Default Description
ENABLE_DEPLOY false Master switch. Deploy never runs unless true.
DEPLOY_MODE local-runner local-runner or ssh
DEPLOY_RUNNER_LABEL deploy-ovh Runner label for local-runner mode
DEPLOY_WORKDIR /opt/${REPO_NAME} Working directory on the VPS
DEPLOY_STRATEGY compose compose, systemd, or script
DEPLOY_COMPOSE_FILE docker-compose.yml Compose file path (relative to workdir)
DEPLOY_SYSTEMD_SERVICE (empty) Systemd service name (required for systemd strategy)
DEPLOY_SCRIPT scripts/deploy.sh Deploy script path (relative to repo root)
DEPLOY_ON_TAG false Also deploy on v* tag pushes

Safety

Branch Protection

The workflow only deploys from the DEFAULT_BRANCH (main). This is enforced in two places:

  1. Trigger filter: on.push.branches: [main]
  2. Branch guard step: Runtime check comparing the actual ref to DEFAULT_BRANCH

Never on Pull Requests

The pull_request event is intentionally absent from the trigger list. Deploy cannot run on PRs.

Protected Branches

For maximum safety, enable branch protection on main in Gitea:

  1. Go to Repository Settings → Branches → Branch Protection
  2. Enable for main
  3. Require: status checks to pass, review approval, no force push
  4. This ensures only reviewed, passing code reaches main → gets deployed

Secrets

  • Local-runner mode: No secrets needed. The runner has local access.
  • SSH mode: Private key is written to a temp file, used once, then deleted. It is never echoed to logs.

Runner Label Examples

Label Description
deploy-ovh OVH VPS runner
vps-prod Production VPS
deploy-hetzner Hetzner VPS runner
staging-runner Staging environment

Troubleshooting

Local Runner Mode

  • Runner registered? Check Gitea Admin → Runners or Repo Settings → Runners
  • Label matches? runs-on in workflow must match the runner's label exactly
  • Runner online? Check runner status in Gitea; restart if offline
  • Label suffix correct? Use :host for direct host execution (not container)
  • Docker accessible? Runner user must be in the docker group
  • Workdir exists? DEPLOY_WORKDIR must exist and be readable
  • Compose file present? Check docker-compose.yml exists in the workdir
  • Permissions? For systemd: runner user needs sudo for systemctl

SSH Mode

  • Secrets set? Check all four secrets are in repo settings
  • Key format? Private key must include -----BEGIN header
  • SSH port open? VPS firewall must allow port 22 (or custom port) from runner
  • User permissions? SSH user needs access to Docker/systemd/workdir
  • Known hosts? Set DEPLOY_KNOWN_HOSTS for production (avoids MITM risk)
  • Key not passphrase-protected? CI keys must have no passphrase

General

  • ENABLE_DEPLOY=true? Check .ci/config.env
  • Pushing to main? Deploy only triggers on DEFAULT_BRANCH
  • Workflow file correct? YAML syntax errors prevent the workflow from running
  • Check Actions tab in Gitea for workflow run logs