feat(cd): add continuous deployment on push to master
Some checks failed
CI / ruff (push) Successful in 8s
CI / pytest (push) Successful in 36m19s
CI / architecture (push) Successful in 11s
CI / dependency-scanning (push) Successful in 27s
CI / audit (push) Successful in 9s
CI / docs (push) Failing after 59s
CI / deploy (push) Failing after 3s

Deploy job SSHes to production after ruff/pytest/architecture pass,
running scripts/deploy.sh (stash, pull, docker rebuild, migrate, health check).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 22:42:13 +01:00
parent 9154eec871
commit 11f1909f68
4 changed files with 195 additions and 42 deletions

View File

@@ -205,10 +205,10 @@ Configure these in your Gitea repository under **Settings > Actions > Secrets**:
| Secret | Description | Used by |
|--------|-------------|---------|
| `SSH_PRIVATE_KEY` | Private key for deployment server | Deploy job (if added) |
| `SERVER_HOST` | Production server IP/hostname | Deploy job |
| `SERVER_USER` | SSH user on production server | Deploy job |
| `SERVER_PATH` | App directory on server | Deploy job |
| `DEPLOY_SSH_KEY` | Ed25519 private key for deployment | Deploy job |
| `DEPLOY_HOST` | Production server IP (e.g. `127.0.0.1`) | Deploy job |
| `DEPLOY_USER` | SSH user on production server (e.g. `samir`) | Deploy job |
| `DEPLOY_PATH` | App directory on server (e.g. `/home/samir/apps/orion`) | Deploy job |
---
@@ -223,16 +223,17 @@ push/PR to master
├── architecture (architecture validation)
├── dependency-scanning (pip-audit, non-blocking)
├── audit (custom audit, non-blocking)
── docs (mkdocs build, master-only, after lint+test pass)
── docs (mkdocs build, master-only, after lint+test pass)
└── deploy (SSH deploy, master-only, after lint+test+arch pass)
```
All jobs run in parallel except `docs`, which waits for `ruff`, `pytest`, and `architecture` to pass.
All jobs run in parallel except `docs` and `deploy`, which wait for `ruff`, `pytest`, and `architecture` to pass. The `deploy` job only runs on push (not PRs).
---
## 9. Adding a Deploy Job (Optional)
## 9. Deploy Job (Continuous Deployment)
To add automated deployment via SSH (similar to the GitLab deploy stage), add this job to `.gitea/workflows/ci.yml`:
The CI pipeline includes an automated deploy job that runs on every successful push to master. It SSHes to the production server and runs a version-controlled deploy script:
```yaml
deploy:
@@ -240,23 +241,28 @@ To add automated deployment via SSH (similar to the GitLab deploy stage), add th
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
needs: [ruff, pytest, architecture]
steps:
- uses: actions/checkout@v4
- name: Deploy via SSH
- name: Deploy to production
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ${{ secrets.SERVER_PATH }}
git pull origin master
source .venv/bin/activate
uv sync --frozen
python -m alembic upgrade head
sudo systemctl restart wizamart
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: 22
command_timeout: 10m
script: cd ${{ secrets.DEPLOY_PATH }} && bash scripts/deploy.sh
```
The `scripts/deploy.sh` script handles the full deploy lifecycle:
1. Stash local changes (preserves `.env` and other server-side edits)
2. Pull latest code (`--ff-only`)
3. Pop stash to restore local changes
4. Rebuild and restart Docker containers (`docker compose --profile full up -d --build`)
5. Run database migrations (`alembic upgrade heads`)
6. Health check `http://localhost:8001/health` with retries
See [Hetzner Server Setup — Step 16](hetzner-server-setup.md#step-16-continuous-deployment) for the full setup guide including SSH key generation and Gitea secrets configuration.
---
## 10. Firewall Configuration