main.
It’s about €7 a month, on one machine. The app, Postgres, Redis and TLS termination all
share the box, and the deployment is eight files feather new already put in your
repository.
This part splits differently from the others. The repository work is a prompt like any
other. The server work is a set of commands you run yourself, and the prompt is told not
to touch them.
What you’ll have
- The app at
https://your-domain, with a certificate that renews itself - A deploy script that migrates once, swaps containers and waits for health
- Nightly database dumps with retention
- A GitHub Actions workflow that tests, then deploys, on every push to
main
Which parts of Feather this uses
Docker layout
What
feather docker init writes, and the two details worth not breaking.Health checks
/health checks the database, which is what makes a health-gated deploy meaningful.Security check
A config audit that exits non-zero, so it works as a pre-launch gate.
Configuration
TRUSTED_HOSTS, OAUTH_CALLBACK_URL and the keys compose sets for you.Step 1: What you need
- A working app from part 4 or part 5
- A domain whose DNS you control
- A VPS you can SSH into as root. Two vCPU and 4 GB is enough to start
- Docker locally
Step 2: Run the prompt
This covers the repository side and prepares the commands you’ll run on the server.The prompt
Step 3: Set up the server yourself
Work through the list the prompt gave you. Two things are worth stating here because getting them wrong is expensive. The production.env never enters git and never goes into the image.
At minimum it needs DOMAIN, POSTGRES_PASSWORD, SECRET_KEY, your Google credentials,
OAUTH_CALLBACK_URL and TRUSTED_HOSTS. Leave FLASK_CONFIG, DATABASE_URL,
REDIS_URL and JOB_BACKEND out of it, because compose sets those on the container and a
duplicate is one more thing to disagree.
Step 4: Test it
https://your-domainloads over a valid certificate.- Sign in with Google. If this works, your proxy headers are right.
curl https://your-domain/healthreturns healthy, including the database check.- Push a commit to
mainand watch the workflow test, then deploy. - Run
./deploy/backup.sh, then restore the dump into the database.
Step 5: The pre-launch review
Worth a second prompt once it’s live.Pre-launch review prompt
What the framework did
Migrations ran exactly once, before the swap.deploy/deploy.sh runs
feather db upgrade in a throwaway container built from the new image, while the old
containers are still serving. A failed migration stops the deploy and leaves the site up.
It’s also why CMD doesn’t run migrations: two web containers starting together would both
begin the same Alembic upgrade, and Alembic doesn’t arbitrate that.
Healthy means the database is reachable. The container healthcheck curls /health,
which Feather registers and which checks the database rather than just confirming Python is
alive. A deploy that waits on it is therefore waiting on something meaningful. See
Health checks.
TLS has no cron job. Caddy obtains and renews certificates for every hostname in the
Caddyfile. What does need attention is X-Real-IP: Caddy sets Host and
X-Forwarded-Proto by default, but not that one, and rate limiting reads it. The generated
config adds it, and deleting that line makes every request look like it came from the same
address.
CI holds no application secrets. The server builds its own images, so the workflow
needs two repository secrets and no registry. Your .env never leaves the box. See
Continuous deployment.
Where to go next
- A staging host. A second A record, a second checkout, a second compose project name, and the same scripts.
- Log aggregation. The app writes JSON logs in production. Ship them somewhere you can search.
- Error alerting.
/admin/logsrecords exceptions. Notify yourself when the rate spikes. - Off-site backups. A dump on the same disk as the database isn’t a backup.