Installation
Four ways to run RedScribe, in order of how much TLS setup they need. All of them share the same First run finishing steps (root key plus first Superadmin) at the end.
| Method | Best for | TLS |
|---|---|---|
| 1. Tailscale | Recommended, fully automated HTTPS | Automatic, via Tailscale |
| 2. Your own cert | Your own domain, cert from anywhere | Bring your own fullchain.pem/privkey.pem |
| 3. Plain HTTP | Local/private-network trial only | None |
| 4. Local dev | Working on RedScribe itself | None (loopback only) |
Common setup (all methods)
Section titled “Common setup (all methods)”Copy the env template and generate its secrets first:
cp .env.example .env# Django's session/CSRF signing key — any random 50-char string workspython3 -c "import secrets, string; print(''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(50)))"Set DJANGO_SECRET_KEY in .env to that value, and POSTGRES_PASSWORD to
something of your own choosing. REDSCRIBE_ROOT_KEY (the instance’s master
key) needs the app’s own generator, so it’s created in First
run, not here.
Method 1: Docker Compose + Tailscale (recommended)
Section titled “Method 1: Docker Compose + Tailscale (recommended)”Best fit if the host is already on a Tailscale tailnet with MagicDNS + HTTPS certs enabled. There’s no separate cert management, no host cron, and no manual renewal ever.
- In
.env, setNGINX_SERVER_NAME=<your-machine>.<tailnet>.ts.net(find it withtailscale status --self --json, fieldDNSName) and add the same hostname toDJANGO_ALLOWED_HOSTS. - Set
DJANGO_SETTINGS_MODULE=config.settings.prodin.env. -
Terminal window docker compose --profile tailscale up -d --build
That’s the whole thing. The cert-renew service issues the first cert and
keeps renewing it for as long as the stack runs (checks every 6h, no-ops
until near expiry); nginx waits for it to exist, then reloads automatically
whenever it changes. Continue at First run.
Want it reachable from the public internet too, via Tailscale Funnel? See Exposing it with Tailscale Funnel in the deployment deep dive. It needs a specific raw-TCP forwarding mode, not Funnel’s default.
Method 2: Docker Compose + your own TLS certificate
Section titled “Method 2: Docker Compose + your own TLS certificate”For your own domain with a cert from anywhere else, be it Let’s Encrypt/certbot, a corporate CA, or whatever you already run.
- In
.env, setNGINX_SERVER_NAME=<your-domain>, add it toDJANGO_ALLOWED_HOSTS, and setDJANGO_SETTINGS_MODULE=config.settings.prod. - Drop your certificate into place under exactly these two filenames (see
certs/README.md):Terminal window cp /path/to/your/fullchain.pem certs/fullchain.pemcp /path/to/your/privkey.pem certs/privkey.pem -
(No
Terminal window docker compose up -d --build--profile tailscaleneeded, since that service only exists for Method 1.)
nginx waits for both files to exist before starting, and reloads
automatically (no restart needed) whenever it notices fullchain.pem’s
content change. That means your own renewal process (e.g. a certbot timer)
is picked up within the hour without touching this stack at all. Continue
at First run.
Method 3: Docker Compose, plain HTTP
Section titled “Method 3: Docker Compose, plain HTTP”No TLS at all. This is fine for trying things out locally or over a private network you already trust, but not for anything reachable beyond that.
docker compose up -d --buildLeave DJANGO_SETTINGS_MODULE=config.settings.dev (the .env.example
default). nginx still starts alongside web but just idles waiting for a
certificate that will never arrive, harmlessly. Skip straight to
http://localhost:8000/ (bound to loopback only, so only reachable from the
host itself) instead of going through nginx. Continue at First
run.
Method 4: Local development without Docker
Section titled “Method 4: Local development without Docker”For working on the app itself without a container rebuild loop:
python3 -m venv .venv && source .venv/bin/activatepip install -r requirements.txtdocker compose up -d db # just the databasecp .env.example .env # fill in secrets as above; POSTGRES_HOST=localhostpython manage.py migratepython manage.py generate_root_key # put the output in .envpython manage.py bootstrap_superadmin --username admin --email admin@example.compython manage.py runserverThis already includes its own root key and Superadmin steps inline, so skip First run, you’re done. Rebuilding the frontend bundles after a template/JS/CSS change is a separate step, see Frontend asset builds. See also Local development for the full dev-environment loop.
Why nginx uses host networking
Section titled “Why nginx uses host networking”nginx’s service in docker-compose.yml runs with network_mode: host
rather than a ports: - "80:80" mapping. See Docker Compose deep
dive for
exactly why this matters for audit logging and login-lockout IP recording.