docs: document deployment and ci cd process
This commit is contained in:
296
DEPLOYMENT.md
296
DEPLOYMENT.md
@@ -1,68 +1,278 @@
|
||||
# Deployment
|
||||
# Deployment and CI/CD
|
||||
|
||||
## Current deployment model
|
||||
- one private GitHub repository
|
||||
- `develop` deploys to the test server
|
||||
- `main` is reserved for production deployment
|
||||
- GitHub Actions uploads the repository contents to the server over SSH
|
||||
- the server does not need GitHub access to deploy
|
||||
|
||||
This is intentional. For a private repository, server-side `git clone` adds unnecessary credential management.
|
||||
|
||||
## Branch strategy
|
||||
- `develop`: test/staging deployments
|
||||
- `main`: production deployments
|
||||
- keep one private GitHub repository
|
||||
- `develop`: test deployment branch
|
||||
- `main`: production branch
|
||||
- feature branches: normal product work
|
||||
|
||||
## Local development
|
||||
- current `docker-compose.yml` remains the development stack
|
||||
- use `.env` or `.env.dev.example` as the template
|
||||
## Environments
|
||||
### Development / test
|
||||
- target server: `192.168.2.55`
|
||||
- deployment path: `/opt/workdock`
|
||||
- stack file: `docker-compose.prod.yml`
|
||||
- env file on server: `.env.test`
|
||||
- current access URL: `http://192.168.2.55:8088`
|
||||
|
||||
## Test deployment server
|
||||
- copy `.env.test.example` to `.env.test` on the server
|
||||
- recommended path: `/opt/workdock/.env.test`
|
||||
- current test target: `192.168.2.55`
|
||||
### Production
|
||||
- same deployment mechanism
|
||||
- different server
|
||||
- env file on server: `.env.prod`
|
||||
- should run behind real HTTPS
|
||||
- should keep `DEBUG=0`
|
||||
|
||||
## Production deployment server
|
||||
- copy `.env.prod.example` to `.env.prod` on the server
|
||||
- use HTTPS and secure cookies in production
|
||||
## Important design choice
|
||||
The current test server is a LAN-only HTTP deployment.
|
||||
|
||||
Because the Django settings enforce secure-cookie checks when `DEBUG=0`, the test deployment uses:
|
||||
- `DJANGO_DEBUG=1`
|
||||
- `RUN_DJANGO_CHECK=0`
|
||||
|
||||
That is acceptable for this internal test box only.
|
||||
|
||||
Production must use:
|
||||
- `DJANGO_DEBUG=0`
|
||||
- `DJANGO_SECURE_COOKIES=1`
|
||||
- HTTPS
|
||||
- `RUN_DJANGO_CHECK=1`
|
||||
|
||||
## Files used for deployment
|
||||
- [docker-compose.prod.yml](/Users/bostame/Documents/workdock-platform/docker-compose.prod.yml)
|
||||
- [scripts/deploy_stack.sh](/Users/bostame/Documents/workdock-platform/scripts/deploy_stack.sh)
|
||||
- [backend/entrypoint-web-prod.sh](/Users/bostame/Documents/workdock-platform/backend/entrypoint-web-prod.sh)
|
||||
- [backend/entrypoint-worker-prod.sh](/Users/bostame/Documents/workdock-platform/backend/entrypoint-worker-prod.sh)
|
||||
- [deploy/Caddyfile](/Users/bostame/Documents/workdock-platform/deploy/Caddyfile)
|
||||
- [.env.test.example](/Users/bostame/Documents/workdock-platform/.env.test.example)
|
||||
- [.env.prod.example](/Users/bostame/Documents/workdock-platform/.env.prod.example)
|
||||
- [.github/workflows/deploy-test.yml](/Users/bostame/Documents/workdock-platform/.github/workflows/deploy-test.yml)
|
||||
- [.github/workflows/deploy-prod.yml](/Users/bostame/Documents/workdock-platform/.github/workflows/deploy-prod.yml)
|
||||
|
||||
## What `deploy_stack.sh` does
|
||||
The deployment script:
|
||||
1. validates the env file exists
|
||||
2. builds `web`, `worker`, and `caddy`
|
||||
3. starts `db` and `redis`
|
||||
4. initializes writable volume ownership for:
|
||||
- `/app/media`
|
||||
- `/app/staticfiles`
|
||||
- `/app/backups`
|
||||
5. runs:
|
||||
- `migrate`
|
||||
- `bootstrap_initial_users`
|
||||
- `collectstatic`
|
||||
6. optionally runs `manage.py check`
|
||||
7. starts:
|
||||
- `web`
|
||||
- `worker`
|
||||
- `caddy`
|
||||
8. waits for `/healthz/`
|
||||
|
||||
## Proxmox / LXC requirement
|
||||
This project is running in an Ubuntu CT on Proxmox, with Docker inside the CT.
|
||||
|
||||
For this to work, the CT needed Proxmox-side configuration in:
|
||||
- `/etc/pve/lxc/<CTID>.conf`
|
||||
|
||||
Required settings:
|
||||
```conf
|
||||
features: nesting=1,keyctl=1
|
||||
lxc.apparmor.profile: unconfined
|
||||
lxc.mount.entry: /dev/null sys/module/apparmor/parameters/enabled none bind 0 0
|
||||
```
|
||||
|
||||
Then restart the CT:
|
||||
```bash
|
||||
pct restart <CTID>
|
||||
```
|
||||
|
||||
Without this, Docker containers in the CT fail with:
|
||||
```text
|
||||
open sysctl net.ipv4.ip_unprivileged_port_start ... permission denied
|
||||
```
|
||||
|
||||
This is a Proxmox/LXC nested-Docker issue, not an application bug.
|
||||
|
||||
## Server bootstrap
|
||||
Install:
|
||||
- `git`
|
||||
- `docker`
|
||||
- `docker compose plugin`
|
||||
- `curl`
|
||||
|
||||
Recommended app directory:
|
||||
- `/opt/workdock`
|
||||
|
||||
## First clone on server
|
||||
Run on the server once:
|
||||
```bash
|
||||
apt-get update
|
||||
apt-get install -y ca-certificates curl gnupg git
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
|
||||
apt-get update
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
systemctl enable --now docker
|
||||
```
|
||||
|
||||
## Server directory layout
|
||||
Current test server path:
|
||||
```bash
|
||||
/opt/workdock
|
||||
```
|
||||
|
||||
Important server-local files:
|
||||
- `/opt/workdock/.env.test`
|
||||
- later `/opt/workdock/.env.prod`
|
||||
|
||||
These env files are intentionally not uploaded from GitHub Actions.
|
||||
|
||||
## Test env file
|
||||
Create on the server:
|
||||
```bash
|
||||
git clone git@github.com:OWNER/REPO.git /opt/workdock
|
||||
cd /opt/workdock
|
||||
cp .env.test.example .env.test
|
||||
```
|
||||
|
||||
## Test deploy manually
|
||||
```bash
|
||||
cd /opt/workdock
|
||||
./scripts/deploy_stack.sh .env.test docker-compose.prod.yml
|
||||
Current important values for the LAN test box:
|
||||
```env
|
||||
DJANGO_DEBUG=1
|
||||
DJANGO_ALLOWED_HOSTS=192.168.2.55,localhost,127.0.0.1
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS=http://192.168.2.55:8088
|
||||
DJANGO_SECURE_COOKIES=0
|
||||
DJANGO_SECURE_SSL_REDIRECT=0
|
||||
APP_PORT=8088
|
||||
SITE_ADDRESS=:80
|
||||
```
|
||||
|
||||
## Production deploy manually
|
||||
```bash
|
||||
cd /opt/workdock
|
||||
./scripts/deploy_stack.sh .env.prod docker-compose.prod.yml
|
||||
Generate strong values for:
|
||||
- `DJANGO_SECRET_KEY`
|
||||
- `POSTGRES_PASSWORD`
|
||||
|
||||
## Production env file
|
||||
Production should use:
|
||||
```env
|
||||
DJANGO_DEBUG=0
|
||||
DJANGO_SECURE_COOKIES=1
|
||||
DJANGO_SECURE_SSL_REDIRECT=1
|
||||
```
|
||||
|
||||
## GitHub Actions secrets
|
||||
### Development environment
|
||||
And a real HTTPS hostname in:
|
||||
- `DJANGO_ALLOWED_HOSTS`
|
||||
- `DJANGO_CSRF_TRUSTED_ORIGINS`
|
||||
- `SITE_ADDRESS`
|
||||
|
||||
## Manual test deployment
|
||||
If you need to deploy manually on the test server:
|
||||
```bash
|
||||
cd /opt/workdock
|
||||
RUN_DJANGO_CHECK=0 DEPLOY_HEALTH_URL="http://127.0.0.1:8088/healthz/" ./scripts/deploy_stack.sh .env.test docker-compose.prod.yml
|
||||
```
|
||||
|
||||
Manual production deployment:
|
||||
```bash
|
||||
cd /opt/workdock
|
||||
RUN_DJANGO_CHECK=1 ./scripts/deploy_stack.sh .env.prod docker-compose.prod.yml
|
||||
```
|
||||
|
||||
## GitHub Actions workflows
|
||||
### Test deployment workflow
|
||||
File:
|
||||
- [deploy-test.yml](/Users/bostame/Documents/workdock-platform/.github/workflows/deploy-test.yml)
|
||||
|
||||
Behavior:
|
||||
- triggers on push to `develop`
|
||||
- can also be run manually with `workflow_dispatch`
|
||||
- checks out the repo in GitHub Actions
|
||||
- uploads the working tree to the server over SSH
|
||||
- runs the server deployment script
|
||||
|
||||
### Production deployment workflow
|
||||
File:
|
||||
- [deploy-prod.yml](/Users/bostame/Documents/workdock-platform/.github/workflows/deploy-prod.yml)
|
||||
|
||||
Behavior:
|
||||
- manual only
|
||||
- uploads the working tree to the production server
|
||||
- runs the production deployment script
|
||||
|
||||
## GitHub environment setup
|
||||
In GitHub:
|
||||
1. open repository settings
|
||||
2. open `Environments`
|
||||
3. create:
|
||||
- `development`
|
||||
- `production`
|
||||
|
||||
### Development environment secrets
|
||||
Add:
|
||||
- `TEST_DEPLOY_HOST`
|
||||
- `TEST_DEPLOY_USER`
|
||||
- `TEST_DEPLOY_SSH_KEY`
|
||||
- `TEST_DEPLOY_PORT`
|
||||
- `TEST_DEPLOY_PATH`
|
||||
- `TEST_DEPLOY_SSH_KEY`
|
||||
|
||||
### Production environment
|
||||
Current test values:
|
||||
- `TEST_DEPLOY_HOST=192.168.2.55`
|
||||
- `TEST_DEPLOY_USER=root`
|
||||
- `TEST_DEPLOY_PORT=22`
|
||||
- `TEST_DEPLOY_PATH=/opt/workdock`
|
||||
- `TEST_DEPLOY_SSH_KEY=<private key that can ssh to root@192.168.2.55>`
|
||||
|
||||
### Production environment secrets
|
||||
Add:
|
||||
- `PROD_DEPLOY_HOST`
|
||||
- `PROD_DEPLOY_USER`
|
||||
- `PROD_DEPLOY_SSH_KEY`
|
||||
- `PROD_DEPLOY_PORT`
|
||||
- `PROD_DEPLOY_PATH`
|
||||
- `PROD_DEPLOY_SSH_KEY`
|
||||
|
||||
## Important note for the test server
|
||||
`.env.test.example` is intentionally configured for an HTTP LAN test deployment.
|
||||
That means `RUN_DJANGO_CHECK=0` is used in the test deploy workflow, because the application security checks require secure cookies when `DEBUG=0`.
|
||||
For real production, use `.env.prod` behind HTTPS and keep `RUN_DJANGO_CHECK=1`.
|
||||
## How the CI/CD test deploy works
|
||||
### Normal flow
|
||||
1. push code to `develop`
|
||||
2. GitHub Actions runs `Deploy Test`
|
||||
3. workflow uploads repository contents to `/opt/workdock`
|
||||
4. server keeps its local `.env.test`
|
||||
5. `deploy_stack.sh` rebuilds and restarts the stack
|
||||
6. workflow succeeds only after `/healthz/` is healthy
|
||||
|
||||
### Manual trigger
|
||||
From GitHub Actions:
|
||||
1. open `Deploy Test`
|
||||
2. click `Run workflow`
|
||||
|
||||
## How to validate a deployment
|
||||
### From your machine
|
||||
```bash
|
||||
curl -I http://192.168.2.55:8088/healthz/
|
||||
```
|
||||
|
||||
### On the server
|
||||
```bash
|
||||
cd /opt/workdock
|
||||
docker compose --env-file .env.test -f docker-compose.prod.yml ps
|
||||
docker compose --env-file .env.test -f docker-compose.prod.yml logs --tail=100 web
|
||||
docker compose --env-file .env.test -f docker-compose.prod.yml logs --tail=100 worker
|
||||
docker compose --env-file .env.test -f docker-compose.prod.yml logs --tail=100 caddy
|
||||
```
|
||||
|
||||
## Rollback
|
||||
This deployment path is source-upload based, not image-tag based.
|
||||
|
||||
Rollback options:
|
||||
1. revert the bad commit on `develop` and let GitHub Actions deploy again
|
||||
2. manually re-upload a previous working checkout and rerun `deploy_stack.sh`
|
||||
|
||||
For production, you may later want image-tag based rollback. That is not necessary yet for the test box.
|
||||
|
||||
## Operational notes
|
||||
- server-local env files must survive deployments
|
||||
- do not store `.env.test` or `.env.prod` in Git
|
||||
- test deployment is intentionally weaker than production on transport security
|
||||
- production should not reuse the test env model
|
||||
|
||||
## Current known-good state
|
||||
Validated manually:
|
||||
- repository pushed to private GitHub
|
||||
- server bootstrap completed
|
||||
- test stack deployed successfully
|
||||
- health check reachable at:
|
||||
- `http://192.168.2.55:8088/healthz/`
|
||||
|
||||
Reference in New Issue
Block a user