Docker
Run a container with persistent data, stable secrets, and a verified client connection.
Run ghcr.io/bloodf/durindoor with a persistent volume. Images support linux/amd64 and linux/arm64. Docker supplies Node.js, so the host does not need a Node installation.
Prepare secrets and storage
Create a private environment file before starting the container:
umask 077
printf '%s=%s\n' \
JWT_SECRET "$(openssl rand -hex 32)" \
API_KEY_SECRET "$(openssl rand -hex 32)" > durindoor.envAdd INITIAL_PASSWORD with a password of your own to durindoor.env. Keep this file private and reuse it for replacement containers. Do not regenerate its secrets during an upgrade.
Mount a host directory or named volume at /app/data. Without a mount, removing the container removes its database and credentials.
Start one container
docker run -d \
--name durindoor \
--env-file durindoor.env \
-p 127.0.0.1:20128:20128 \
-v durindoor-data:/app/data \
-e DATA_DIR=/app/data \
ghcr.io/bloodf/durindoor:latestUse latest for evaluation. For production, replace it with a verified numbered tag such as X.Y.Z, or pin the verified image digest. Image version tags omit the git tag's v prefix. A numbered tag can be republished; a digest is the immutable reference.
For a host bind, replace the volume argument with -v "$HOME/.durindoor:/app/data". That path is an example, while the native default remains ~/.9router.
Verify the container
curl -fsS http://localhost:20128/api/health
docker logs --tail 100 durindoorExpect {"ok":true}. Open http://localhost:20128/dashboard and sign in with your saved password. Add a provider, mint a DurinDoor API key, and complete the inference smoke test.
The image listens on 0.0.0.0 inside the container. The host publication above restricts access to loopback. For remote access, use TLS and the security checklist.
Use Compose
Start with the repository's docker-compose.yml and review it before use:
curl -fsSL https://raw.githubusercontent.com/bloodf/durindoor/main/docker-compose.yml -o docker-compose.ymlPoint its env_file at durindoor.env, pin image, and change ports to 127.0.0.1:20128:20128 for a local install.
For SQLite, leave DURINDOOR_PG_URL unset or empty and do not set DURINDOOR_DATABASE_ENGINE=postgres. Packaged startup normalizes empty environment values to unset, so the shipped empty Compose default permits SQLite. A nonempty URL or the explicit engine selector enables PostgreSQL-only startup. For an existing SQLite database, use the dashboard PostgreSQL cutover before enabling that mode.
docker compose up -d durindoor
docker compose logs --tail 100 durindoorCompose mounts the named volume durindoor-data at /app/data. It does not define a DurinDoor health check. PostgreSQL profiles are optional and have their own volumes; follow the Postgres runbook before starting one.
Check optional browser support
The image includes Chromium and Xvfb for ChatGPT Web. Xvfb starts when needed unless DURINDOOR_XVFB=0. A custom build with CHATGPT_WEB_BROWSER=false omits those browser packages. Other browser requirements remain provider-specific.
If the dashboard is blank or static files return 404, check the image contents using Reverse proxy and static assets. Changing an Alpine mirror is a local build option (ALPINE_MIRROR), not a runtime environment setting.
Upgrade or recover
Record the current image:
docker inspect durindoor --format '{{.Config.Image}}'Stop the container and back up its volume. Pull the chosen image, update Compose, and run docker compose up -d durindoor. Reuse the same volume and environment file. Verify health, inference, providers, keys, and usage.
For a failed upgrade, restore the pre-upgrade backup before starting the previous image. Starting an old image alone does not undo a database migration. Full procedure: Upgrading.