flask_template_codex/DEPLOYMENT.md
2026-08-08 06:08:24 +02:00

158 lines
5.3 KiB
Markdown

# Deploy Steady with Docker Compose and Caddy
This repository is ready for the established VPS layout:
- repository directory: `~/stacks/steady`
- container/service name: `steady`
- public address: `https://steady.bujour.de`
- private Docker port: `5000` (it is not published on the VPS)
- persistent SQLite volume: `steady_data`, mounted at `/app/instance`
- reverse proxy: Caddy on the external Docker network `proxy`
The container runs database migrations before Gunicorn starts. Caddy terminates
TLS and forwards requests over the private Docker network.
## 1. Prerequisites
Install Git, Docker Engine with the Compose plugin, and Caddy in its existing
Compose stack. DNS for `steady.bujour.de` must resolve to the VPS, and inbound
ports 80 and 443 must be allowed by both the VPS firewall and provider firewall.
Do not open port 5000.
Confirm the shared proxy network exists:
```bash
docker network inspect proxy >/dev/null 2>&1 || docker network create proxy
```
## 2. Clone the application
Replace the example repository URL with the actual Git remote:
```bash
mkdir -p ~/stacks
git clone <repository-url> ~/stacks/steady
cd ~/stacks/steady
```
For an existing checkout, use `git pull --ff-only` instead of cloning it again.
## 3. Create the production environment
Compose automatically reads `~/stacks/steady/.env`. Create it on the VPS; do
not commit it:
```bash
cd ~/stacks/steady
umask 077
python -c "import secrets; print('SECRET_KEY=' + secrets.token_urlsafe(48))" > .env
printf '%s\n' 'TRUSTED_HOSTS=steady.bujour.de' >> .env
```
`compose.yaml` supplies the remaining production settings, including the exact
SQLite URL `sqlite:////app/instance/steady.db`. The named volume therefore
persists the database across container replacement.
Validate the rendered configuration without printing the secret in shared logs:
```bash
docker compose config --quiet
```
## 4. Build and start
```bash
docker compose build --pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 steady
```
Startup fails if the migration fails, so do not ignore an unhealthy or restarting
container. Check the private endpoint from another container on the proxy network:
```bash
docker run --rm --network proxy curlimages/curl:8.14.1 -fsS http://steady:5000/health
```
The expected body is `{"status":"ok"}`. `curl localhost:5000` intentionally
does not work because the application port is not published to the host.
## 5. Configure Caddy
Add this site block to the existing Caddyfile in `~/stacks/caddy/`:
```caddyfile
steady.bujour.de {
reverse_proxy steady:5000
}
```
Ensure the Caddy service also joins the external `proxy` network. Validate and
reload Caddy using its actual Compose service name (the examples assume
`caddy`):
```bash
cd ~/stacks/caddy
docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
```
Caddy obtains and renews the TLS certificate automatically after DNS and ports
80/443 are correct.
## 6. Verify the public deployment
```bash
curl -fsS https://steady.bujour.de/health
curl -I https://steady.bujour.de/
```
Confirm the health JSON, a valid HTTPS certificate, and security headers such as
`Strict-Transport-Security`, `Content-Security-Policy`, and
`X-Content-Type-Options`. Then register an account and exercise a normal task
workflow in the browser.
## Updates
Back up first, then pull, rebuild, and replace the service:
```bash
cd ~/stacks/steady
docker compose exec -T steady python -c "import sqlite3; source=sqlite3.connect('/app/instance/steady.db'); backup=sqlite3.connect('/app/instance/steady-backup.db'); source.backup(backup); backup.close(); source.close()"
git pull --ff-only
docker compose build --pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 steady
```
Copy the backup off the named volume to protected storage as part of the VPS
backup process. The in-volume backup is only an immediate pre-update snapshot.
Test restore procedures regularly.
## Operations and rollback
- Probe `https://steady.bujour.de/health` for process availability.
- View logs with `docker compose logs --tail=200 steady` or add `-f` to follow.
- Restart with `docker compose restart steady`.
- Keep `FEATURE_ADMIN=false` unless the guarded, data-free placeholder is needed.
- Never expose Flask/Gunicorn directly or use `flask run` in production.
- Rotate `SECRET_KEY` only when intentionally invalidating all sessions.
Application rollback requires both the prior Git revision/image and a compatible
database backup. Do not run destructive migration downgrades without reviewing
the migration and recovery plan.
## Troubleshooting
- **Compose says `SECRET_KEY` is missing:** create `.env` as shown above.
- **Container restarts:** inspect `docker compose logs steady`; migration or
configuration errors appear before Gunicorn starts.
- **Caddy returns 502:** verify both containers join `proxy`, the service is
healthy, and the upstream is exactly `steady:5000`.
- **Host rejected with HTTP 400:** make `TRUSTED_HOSTS` match the public hostname.
- **No certificate:** verify public DNS and inbound ports 80/443; inspect Caddy's
logs, not the application logs.
- **Permission error for SQLite:** verify `/app/instance` is the `steady_data`
volume. The image runs as the unprivileged user `steady` (UID 10001).