Deployment¶
ALMa was built to run on 127.0.0.1. If you want to access it from
another machine — your laptop reaching a desktop, a tablet via a
home VPN — you'll need a reverse proxy and an API key.
Reverse proxy¶
Pick one of:
- Caddy — the easiest. Auto-TLS via Let's Encrypt.
- Nginx — proven and ubiquitous.
- Traefik — good fit for Docker compose stacks.
- Tailscale Funnel — if your machine is on Tailscale and you want to expose to the internet.
Minimal Caddyfile:
The same with Nginx:
server {
listen 443 ssl http2;
server_name alma.example.com;
ssl_certificate /etc/letsencrypt/live/alma.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/alma.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
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;
}
}
API key¶
When ALMa is reachable from outside 127.0.0.1, set an API key:
Restart the backend. Every request now requires:
Without the header, the API returns 401.
The frontend SPA reads the key from window.localStorage.api_key.
Set it once via the browser console:
You only do this once per browser; subsequent requests carry the header automatically.
Docker production¶
The shipped docker-compose.yml already encodes the production
defaults you want: restart: unless-stopped, localhost-only port
binding (127.0.0.1:8000:8000), read-only rootfs, cap_drop ALL,
no-new-privileges, healthcheck on /api/v1/health, log rotation,
and per-host resource limits (ALMA_CPUS / ALMA_MEMORY). For most
deployments, the right move is to pull the prebuilt GHCR image
through the shipped overlay rather than rebuilding locally:
git clone https://github.com/costantinoai/alma-library-manager.git
cd alma-library-manager
cp .env.example .env # API_KEY, OPENALEX_EMAIL, etc.
# Pin a specific version in production (avoid surprise upgrades)
ALMA_IMAGE_TAG=0.20.1 \
docker compose -f docker-compose.yml -f docker-compose.ghcr.yml up -d
State lives in the alma-data / alma-config named volumes (owned by
the container app user — no host permission setup). See
Getting started → Docker
for the full list of compose flags (GPU overlay, lite image, build
locally, etc.).
The image is multi-stage: a builder layer compiles the frontend,
the runtime layer carries only the Python app + the built
frontend. The full image (with torch + CUDA) is large (~2 GB
on-disk); the lite flavour, which drops the AI stack, is ~300 MB.
Secrets¶
.env—chmod 600, owned by the user that runs the container.data/secrets.json— auto-managed; same permissions.- Never commit either to git. Both are in
.gitignore. - For team / shared deployments, use a secrets manager
(Vault, 1Password CLI, etc.) and template into
.envat start-up.
Process supervision¶
When running outside Docker, use a supervisor:
- systemd (Linux) — example unit:
[Unit]
Description=ALMa
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/alma
EnvironmentFile=/opt/alma/.env
ExecStart=/opt/alma/.venv/bin/uvicorn alma.api.app:app --host 127.0.0.1 --port 8000
Restart=on-failure
User=alma
[Install]
WantedBy=multi-user.target
- launchd (macOS) — wrap in a
.plistwithKeepAlive=true.
Don't use nohup uvicorn … long-term; it has no restart-on-crash.
Updating¶
Docker (the normal path) — deploy a released version with the
versioned script; it pulls ghcr.io/costantinoai/alma-library-manager:
X.Y.Z[-gpu|-lite] (flavor autodetected), recreates the alma
container preserving your env and the alma-data / alma-config
volumes, health-polls, and verifies the container reports the
requested release version:
Bare metal:
git pull
pip install -e ".[ai]" # if AI extras already installed
cd frontend && npm install && npm run build && cd ..
# restart the service
systemctl restart alma # or docker compose up -d
Schema migrations run on backend start-up. If a migration fails, the backend exits non-zero — check the systemd / docker logs.
Cutting a release (maintainer): write docs/releases/vX.Y.Z.md,
then run scripts/release.sh X.Y.Z — preflight, tests, bump, tag,
locally-signed connector built from the tag, push, GitHub release.
Fully local; no CI secrets. Details in extension/README.md →
"How releases work".
Backups¶
See Backups. The single most important habit on a
deployed install: a weekly cron that calls
POST /api/v1/library-mgmt/backup and keeps the last N snapshots.
# /etc/cron.weekly/alma-backup
#!/usr/bin/env bash
# Backups are auto-named with a server timestamp; there is no name param.
curl -fsS -X POST \
-H "X-API-Key: ${ALMA_API_KEY}" \
http://127.0.0.1:8000/api/v1/library-mgmt/backup
What not to do¶
- Don't expose
8000directly to the internet. Even with an API key, run behind TLS at the proxy. - Don't share
data/scholar.dbacross two ALMa instances. SQLite WAL doesn't survive concurrent writers from two processes. - Don't run as
rootin Docker. The compose file already pinsuser: "10001:10001"(the fixed image UID/GID). See Docker volume ownership. - Don't disable the migration check on start-up. Old DBs occasionally need a column added; the check is what makes that safe.