Security and networking
What's exposed
| Port | Service | Who should reach it |
|---|---|---|
8410 | Backend API | Every machine running Chronicler |
11434 | Model runtime | Nothing outside the server |
5432 | Database | Nothing — bound to 127.0.0.1 |
Only 8410 needs to be reachable. If the compose file you're using publishes
11434 more widely than you'd like, restrict it the same way Postgres is:
ports:
- "127.0.0.1:11434:11434"
Claim the server immediately
A server with no users offers Register your admin account to whoever reaches
it first. Between docker compose up -d and creating that account, anyone who
can reach port 8410 can take it.
So: start the stack and register the admin in the same sitting, from a machine you trust, before opening the port to a wider network.
On a local network
For a server and its users on the same trusted LAN, plain HTTP on 8410 is
usually accepted — the traffic never leaves the building. Restrict it to the
subnet at the firewall:
# Linux, ufw — allow only the office subnet
sudo ufw allow from 192.168.1.0/24 to any port 8410
Colleagues then point Settings → Connection → Local at
http://192.168.1.50:8410.
Over the internet: use HTTPS
Put a reverse proxy in front. Chronicler ships no proxy and no certificates — that stays your choice.
server {
listen 443 ssl;
server_name chronicler.example.com;
ssl_certificate /etc/letsencrypt/live/chronicler.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chronicler.example.com/privkey.pem;
client_max_body_size 512M; # uploads
location / {
proxy_pass http://127.0.0.1:8410;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s; # answers can take minutes on CPU
}
}
chronicler.example.com {
reverse_proxy 127.0.0.1:8410 {
transport http {
read_timeout 600s
}
}
request_body {
max_size 512MB
}
}
Two settings matter and are easy to miss: a generous body size (documents are large) and a long read timeout (a CPU-generated answer can take minutes; the usual 60-second default will cut it off).
Then bind the backend to loopback only, so the proxy is the only way in:
ports:
- "127.0.0.1:8410:8410"
Users enter https://chronicler.example.com.
Secrets to protect
| What | Where | Why it matters |
|---|---|---|
POSTGRES_PASSWORD | .env | The only key to your database volume. Lose it and the data is unreadable; leak it and anyone who can reach the database has everything. |
| Backup archives | backend_uploads, plus your offsite folder | A full copy of the database. Protect them like the database itself. |
| Licence key | Admin console | Bound to one install, but still yours. |
The server's signing key is generated on first start and kept in the
backend_uploads volume. You never handle it; just don't delete that volume.
Accounts
- Nobody can sign themselves up. Administrators create accounts (Managing users).
- New accounts get a one-time password, shown once, and must set their own on first sign-in.
- Suspend accounts when people leave — suspension keeps their documents, deletion doesn't.
- Keep the number of administrators small. Administrators can restore backups, which means reading everything.
Keeping it patched
Update monthly (Updating), keep Docker itself updated, and patch the host OS. The stack is small — four containers, no plugins, no extensions — which is the point.
A short checklist
- Admin account registered immediately after first start
- Port 8410 restricted to the network that needs it
- HTTPS in front if it's reachable from outside the LAN
-
.envbacked up somewhere safe, separate from the server - Scheduled backups on, with an offsite copy
- One restore rehearsed at least once
- Administrator accounts kept to a minimum