# Deployment Guide ## Requirements - Docker 24.0+ - Docker Compose 2.20+ - A reverse proxy that terminates TLS for the public hostname - A writable checkout directory for `./data` and `./content` ## Fresh Deployment ```bash git clone https://github.com/your-org/cairnquire.git cd cairnquire cp .env.example .env ``` Edit `.env` and set the HTTPS origin that users will open in their browser: ```dotenv CAIRNQUIRE_PUBLIC_ORIGIN=https://cairnquire.example.com ``` Start the application and verify the local container health endpoint: ```bash docker compose up -d --build docker compose ps curl http://127.0.0.1:8080/health ``` Compose starts Mailpit for local SMTP capture. Open `http://127.0.0.1:8025` to inspect password-reset and notification messages. SMTP is also exposed on `127.0.0.1:1025` for host-run development servers. Then open `https://cairnquire.example.com/setup`. On an empty database the first-run wizard: 1. Creates the initial administrator account. 2. Sets its password. 3. Records whether visitors may create their own accounts. 4. Signs the administrator in. The setup endpoint is only available until the first administrator has been created. The signup setting can be changed later from the administrator's account page. ## Environment Variables The checked-in Compose file sets the container paths. For a normal single-host deployment, only `CAIRNQUIRE_PUBLIC_ORIGIN` must be supplied in `.env`. | Variable | Required | Compose default | Description | |----------|----------|-----------------|-------------| | `CAIRNQUIRE_PUBLIC_ORIGIN` | Yes | None | Exact external HTTPS origin. Passkeys and device-flow URLs depend on this value. | | `CAIRNQUIRE_EMAIL_SMTP_HOST` | No | `mailpit` | SMTP server used for notifications. | | `CAIRNQUIRE_EMAIL_SMTP_PORT` | No | `1025` | SMTP server port. | | `CAIRNQUIRE_EMAIL_FROM` | No | `notifications@cairnquire.local` | Notification sender address. | | `CAIRNQUIRE_LOG_LEVEL` | No | `INFO` | Application log level. | The server also accepts these lower-level overrides when it is run outside the checked-in Compose deployment: | Variable | Default | Description | |----------|---------|-------------| | `CAIRNQUIRE_SERVER_ADDR` | `:8080` | Listen address. | | `CAIRNQUIRE_DATABASE_PATH` | `../../data/db.sqlite` | Local SQLite/libsql database path. | | `CAIRNQUIRE_DATABASE_PRIMARY_URL` | Empty | Optional remote libsql primary URL for embedded-replica mode. | | `CAIRNQUIRE_DATABASE_AUTH_TOKEN` | Empty | Optional auth token for the remote libsql primary. | | `CAIRNQUIRE_CONTENT_SOURCE_DIR` | `../../content` | Writable Markdown source directory. | | `CAIRNQUIRE_CONTENT_STORE_DIR` | `../../data/files` | Content-addressed attachment store. | | `CAIRNQUIRE_CONFIG` | Empty | Optional JSON configuration file. Environment variables override it. | | `CAIRNQUIRE_DEV_MODE` | `false` | Local-only auth shortcut. Never enable this on a deployed server. | ## Persistent Data Compose mounts: - `./data:/workspace/data` for the database and attachments - `./content:/workspace/content` for editable Markdown source Both mounts must remain writable because uploads, browser edits, and sync writebacks modify them. ## Reverse Proxy Expose container port `8080` through a TLS-terminating reverse proxy. Preserve the original hostname and forwarded client address headers. The configured `CAIRNQUIRE_PUBLIC_ORIGIN` must exactly match the external origin, including `https://`. ## Backup And Restore Stop writes or stop the app container before copying a fully consistent local database backup: ```bash docker compose stop app tar czf cairnquire-backup.tgz data content docker compose up -d app ``` Restore by stopping the app, replacing `data` and `content` from the archive, and starting the app again. ## Upgrade ```bash docker compose stop app tar czf cairnquire-backup-before-upgrade.tgz data content git pull docker compose up -d --build curl http://127.0.0.1:8080/health ``` Database migrations run automatically on startup. ## Troubleshooting ```bash docker compose logs -f app docker compose ps curl http://127.0.0.1:8080/health ``` If passkey creation fails after deployment, confirm that the browser URL and `CAIRNQUIRE_PUBLIC_ORIGIN` are the same HTTPS origin.