1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84# Reference single-machine deployment: SQLite + local filesystem +
# one configured model provider is enough for a single-user deployment.
#
# docker compose up
#
# Set the referenced variables in a .env file (see .env.example) first.
services:
iris:
build: .
image: equalify-iris:latest
ports:
- "8080:8080"
environment:
IRIS_UPSTREAM_REPO: ${IRIS_UPSTREAM_REPO}
# The deployment's one GitHub identity. Required โ the container exits at startup
# without it. See .env.example.
IRIS_GITHUB_TOKEN: ${IRIS_GITHUB_TOKEN}
# The shared secret callers must present, or blank for an open deployment (which is
# what the browser app at / needs). Blank on a public URL means public.
IRIS_API_TOKEN: ${IRIS_API_TOKEN:-}
# Number of reverse proxies in front of this container (1 behind a single
# Caddy/nginx). Blank trusts nothing, which makes every caller behind a proxy
# share one rate-limit bucket. See .env.example.
IRIS_TRUST_PROXY: ${IRIS_TRUST_PROXY:-}
# GET /v1/quality's own secret. Blank (default) makes that endpoint 404. Listed here
# because .env.example offers it, and a variable this file drops is one an operator
# sets and then cannot find.
IRIS_QUALITY_TOKEN: ${IRIS_QUALITY_TOKEN:-}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
# For Bedrock instead, supply the standard AWS credential env vars.
# Come back up after a crash or a host reboot, but not after `docker compose stop` โ
# a deliberate stop should stay stopped. `on-failure:N` would give up after N tries;
# surviving a reboot is worth more here, and it is what `on-failure` does not do (a
# reboot stops the container cleanly, so there is no failure to retry).
#
# The cost, worth knowing before you read the log: a startup error that will never
# come right on its own โ a missing IRIS_GITHUB_TOKEN, an unwritable ./data โ becomes
# a loop rather than one exit. `docker compose logs` shows the same message repeating,
# and each of those messages says what to change.
restart: unless-stopped
# Report the container unhealthy when the service stops answering, so `docker ps` and any
# supervisor above it can tell "running" from "working". The command is the Dockerfile's
# HEALTHCHECK; it is repeated here only to give this single-machine deployment its own
# timings, and both poll /v1/health, which is mounted above the rate limiter for exactly
# this caller.
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:8080/v1/health').then(r => process.exit(r.ok ? 0 : 1), () => process.exit(1))",
]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
volumes:
# Persist sessions/, tmp/, and the SQLite DB across restarts.
#
# The container runs as uid 1000 (the image's `node` user, see Dockerfile), and a
# bind-mounted host directory keeps its HOST ownership. On Linux, `./data` must
# therefore be writable by uid 1000, and it fails at STARTUP rather than on the first
# upload: src/index.ts creates sessions/ and tmp/ at import, so the container exits
# before binding the port and `restart:` below turns that into a loop. The log says
# which chown to run. Two remedies: `sudo chown -R 1000:1000 ./data`, or add
# `user: "1234:1234"` to this service to run as whichever uid owns the directory.
#
# A fresh clone already contains `./data` (data/.gitkeep is tracked for exactly this
# reason), so the daemon never has to create the mount source itself โ which it would
# make root-owned, failing for every host user rather than only for uid != 1000.
#
# On macOS and Windows nothing is needed: Docker runs in a VM whose file sharing
# remaps ownership, so `./data` fills up owned by you.
- ./data:/app/data
# The agent library is a git checkout modified only by `git pull`
# (README.md "Configuration"). Iris never writes here โ `src/store/paths.ts` says every
# path it writes is under `data_dir`, and an agent a session builds goes to
# `tmp/<id>/agents/` โ so this mount only has to be readable by uid 1000.
- ./agents:/app/agents
# Override the default env-driven config with your own if desired.
# - ./config.yaml:/app/config.yaml:ro