feat: branch-based deployment (main → staging, production → prod)

- main branch: auto-deploy to Dokku staging
- production branch: build Docker image and deploy to K8s production
- Add DEPLOYMENT.md with workflow documentation
- Create production branch for manual production deployments

Breaking change: pushing to main no longer deploys to production
This commit is contained in:
Graham McIntire 2026-03-06 12:18:50 -06:00
parent 3a5da6f68c
commit 721c1f2481
No known key found for this signature in database
2 changed files with 158 additions and 5 deletions

View file

@ -1,9 +1,10 @@
name: Build
name: Build and Deploy
on:
push:
branches:
- main
- production
env:
DOCKER_BUILDKIT: 1
@ -14,7 +15,30 @@ concurrency:
cancel-in-progress: true
jobs:
build:
deploy-staging:
name: Deploy to Dokku Staging
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history needed for Dokku
- name: Deploy to Dokku
env:
SSH_PRIVATE_KEY: ${{ secrets.DOKKU_SSH_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H 204.110.191.231 >> ~/.ssh/known_hosts
git remote add dokku dokku@204.110.191.231:towerops || true
git push dokku main:main --force
build-production:
name: Build and Deploy to Production
if: github.ref == 'refs/heads/production'
runs-on: ubuntu-22.04
steps:
- name: Checkout code
@ -41,7 +65,7 @@ jobs:
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
IMAGE=${{ secrets.REGISTRY_URL }}/${{ github.repository }}
echo "image=${IMAGE}" >> $GITHUB_OUTPUT
echo "tag=${IMAGE}:main-${TIMESTAMP}-${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "tag=${IMAGE}:production-${TIMESTAMP}-${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "latest_tag=${IMAGE}:latest" >> $GITHUB_OUTPUT
echo "cache_tag=${IMAGE}:buildcache" >> $GITHUB_OUTPUT
@ -49,6 +73,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: k8s/Dockerfile
push: true
tags: |
${{ steps.meta.outputs.tag }}
@ -65,6 +90,6 @@ jobs:
if ! git diff --cached --quiet; then
git commit -m "chore: update towerops-web image to ${{ steps.meta.outputs.tag }} [skip ci]"
# Pull with rebase to handle concurrent pushes
git pull --rebase origin main
git push origin HEAD:main
git pull --rebase origin production
git push origin HEAD:production
fi

128
DEPLOYMENT.md Normal file
View file

@ -0,0 +1,128 @@
# Deployment Guide
## Overview
Towerops uses a branch-based deployment strategy:
- **`main` branch** → Dokku staging environment (automatic)
- **`production` branch** → Kubernetes production cluster (automatic)
## Staging Deployment (Dokku)
### Automatic
Push or merge to `main` branch automatically deploys to Dokku staging:
```bash
git push origin main
```
The Forgejo Actions workflow pushes to the Dokku remote: `dokku@204.110.191.231:towerops`
### Manual
You can also deploy manually:
```bash
git push dokku main
```
## Production Deployment (Kubernetes)
### Process
To deploy to production:
1. **Merge `main` into `production` branch:**
```bash
git checkout production
git merge main
git push origin production
```
2. **Forgejo Actions will automatically:**
- Build Docker image with tag `production-<timestamp>-<commit>`
- Push image to container registry (`git.mcintire.me`)
- Update `k8s/deployment.yaml` with new image tag
- Commit and push the update back to `production` branch
- Kubernetes (via FluxCD) picks up the change and deploys
3. **Monitor the deployment:**
```bash
kubectl get pods -n towerops -w
```
### Rollback
To rollback to a previous version:
```bash
# Find the commit with the working version
git log k8s/deployment.yaml
# Reset to that commit
git checkout production
git reset --hard <commit-sha>
git push origin production --force
```
Or manually update the image tag in `k8s/deployment.yaml` and push.
## Required Secrets
### Forgejo Actions Secrets
Configure these in your Forgejo repository settings:
- `DOKKU_SSH_KEY` - SSH private key for Dokku deployments
- `REGISTRY_URL` - Container registry URL (e.g., `git.mcintire.me`)
- `REGISTRY_USER` - Registry username
- `REGISTRY_PASSWORD` - Registry password/token
### Kubernetes Secrets
These must exist in the `towerops` namespace:
- `forgejo-registry` - Docker registry credentials
- `towerops-secrets` - Application secrets (RELEASE_COOKIE, SECRET_KEY_BASE, CLOAK_KEY)
- `towerops-db` - Database connection
- `towerops-aws` - AWS credentials
- `towerops-billing` - Stripe API keys (optional)
- `towerops-redis` - Redis/Valkey connection
## Branch Protection
Recommended branch protection rules:
### `main` branch
- Require pull request reviews
- Require status checks to pass
- Auto-deploys to staging on merge
### `production` branch
- Require pull request reviews
- Require status checks to pass
- **Only merge from `main`**
- Auto-deploys to production on merge
## Troubleshooting
### Staging deployment fails
Check Forgejo Actions logs and Dokku logs:
```bash
ssh dokku@204.110.191.231 logs towerops
```
### Production deployment fails
1. Check Forgejo Actions logs for build errors
2. Check if image was pushed to registry
3. Check Kubernetes pod status:
```bash
kubectl get pods -n towerops
kubectl logs -n towerops deployment/towerops
```
### Image not updating in Kubernetes
1. Verify `k8s/deployment.yaml` has the new image tag
2. Check FluxCD reconciliation:
```bash
kubectl get gitrepositories -n flux-system
kubectl get kustomizations -n flux-system
```
3. Manual rollout restart if needed:
```bash
kubectl rollout restart deployment/towerops -n towerops
```