296 lines
9.1 KiB
Markdown
296 lines
9.1 KiB
Markdown
# 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
|
|
|
|
### Mode A: Local Runner (Recommended)
|
|
|
|
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)
|
|
|
|
```bash
|
|
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`
|
|
|
|
```bash
|
|
sudo systemctl restart my-service
|
|
```
|
|
|
|
Best for: Applications managed as systemd services (e.g., a Go binary,
|
|
Python app with gunicorn, etc.).
|
|
|
|
### `script`
|
|
|
|
```bash
|
|
./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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```yaml
|
|
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:**
|
|
```bash
|
|
sudo systemctl restart act_runner
|
|
```
|
|
|
|
#### Step 4: Update workflow runs-on
|
|
|
|
In `.gitea/workflows/deploy.yml`, the `deploy-local-runner` job has:
|
|
```yaml
|
|
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`:
|
|
```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:
|
|
```bash
|
|
# 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:
|
|
```bash
|
|
# The service must exist
|
|
systemctl cat my-service
|
|
```
|
|
|
|
For `script` strategy:
|
|
```bash
|
|
# The script must be in the repo and executable
|
|
```
|
|
|
|
### SSH Mode
|
|
|
|
#### Step 1: Create an SSH key
|
|
|
|
```bash
|
|
ssh-keygen -t ed25519 -C "deploy@gitea" -f deploy_key -N ""
|
|
```
|
|
|
|
#### Step 2: Add the public key to the VPS
|
|
|
|
```bash
|
|
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`:
|
|
```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
|