Production Deployment
Contents
Never run python3 app.py in production โ it starts Flask's development server which is single-threaded and not safe for public traffic. Always use gunicorn.
Coolify (recommended)
Coolify is a self-hosted PaaS that handles Docker deployments, SSL, and reverse proxying.
The easiest setup uses the pre-built image from GHCR โ Coolify pulls it directly without building anything on your server:
- Add a new resource โ Docker Image
- Set image to
ghcr.io/airenare/inkstone:latest - Set environment variables:
VAULT_REPO,SECRET_KEY,HIDE_ATTRIBUTION, etc. - Set the exposed port to
8000 - Enable Let's Encrypt for automatic SSL
- Deploy
The container entrypoint clones your vault on first start and pulls it on subsequent starts. No build step, no source checkout needed.
Use a commit SHA tag (e.g. ghcr.io/airenare/inkstone:abc1234) instead of latest if you want explicit control over which InkStone version runs. Available tags are listed on the GHCR package page.
Keeping InkStone up to date
When a new InkStone version is released, a fresh image is pushed to GHCR automatically. To pick it up:
- Manual โ click Redeploy in Coolify. Since the image is already built, it takes a few seconds.
- Scheduled โ in your Coolify app, enable Scheduled Deployments and set a cron expression (e.g.
0 3 * * *for nightly at 3 AM). Coolify will pull:latestand redeploy on that schedule.
There is no mechanism for instant push-triggered updates to third-party deployments โ that would require adding a secret to the InkStone repo itself, which only the maintainer can do. Scheduled redeployment is the practical alternative.
Webhook for live updates
To update the site content without redeploying:
- Set
WEBHOOK_SECRETin your environment:bash WEBHOOK_SECRET=a-random-secret-string - Add a GitHub webhook on your vault repository:
- Payload URL:
https://yourdomain.com/webhook - Content type:
application/json - Secret: same value as
WEBHOOK_SECRET - Events: Just the push event
On every push to the vault repo, GitHub sends a POST to /webhook. InkStone validates the signature with WEBHOOK_SECRET, then pulls the latest vault content and reloads โ no container restart needed.
Subpath hosting
If InkStone is hosted at a path prefix rather than the root (e.g. https://example.com/inkstone/):
URL_PATH_PREFIX=/inkstone
All generated attachment URLs, nav links, and feeds will include the prefix.
SSL
InkStone itself is HTTP only. SSL termination should be handled by a reverse proxy:
- Coolify โ built-in Let's Encrypt
- Caddy โ automatic HTTPS with a
Caddyfile - Nginx โ configure as a proxy pass to
localhost:8000with Certbot
SECRET_KEY
Set a long random string to persist visitor sessions (theme preference, unlocked private notes) across restarts:
python3 -c "import secrets; print(secrets.token_hex(32))"
Without it, every server restart invalidates all sessions.
InkStone