Skip to content

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)

Copy the env template and generate its secrets first:

Terminal window
cp .env.example .env
Terminal window
# Django's session/CSRF signing key — any random 50-char string works
python3 -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.

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.

  1. In .env, set NGINX_SERVER_NAME=<your-machine>.<tailnet>.ts.net (find it with tailscale status --self --json, field DNSName) and add the same hostname to DJANGO_ALLOWED_HOSTS.
  2. Set DJANGO_SETTINGS_MODULE=config.settings.prod in .env.
  3. 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.

  1. In .env, set NGINX_SERVER_NAME=<your-domain>, add it to DJANGO_ALLOWED_HOSTS, and set DJANGO_SETTINGS_MODULE=config.settings.prod.
  2. Drop your certificate into place under exactly these two filenames (see certs/README.md):
    Terminal window
    cp /path/to/your/fullchain.pem certs/fullchain.pem
    cp /path/to/your/privkey.pem certs/privkey.pem
  3. Terminal window
    docker compose up -d --build
    (No --profile tailscale needed, 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.

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.

Terminal window
docker compose up -d --build

Leave 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:

Terminal window
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
docker compose up -d db # just the database
cp .env.example .env # fill in secrets as above; POSTGRES_HOST=localhost
python manage.py migrate
python manage.py generate_root_key # put the output in .env
python manage.py bootstrap_superadmin --username admin --email admin@example.com
python manage.py runserver

This 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.

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.