Skip to main content

Deploying

Installer​

./install.sh walks through binary download, Docker Compose, or a source build. It can pick a listen IP from host interfaces, set library and data paths, write /etc/athenaeum/athenaeum.env, and install a service unit.

curl -fsSL https://raw.githubusercontent.com/Quad4-Software/Athenaeum/master/install.sh | bash

curl -fsSL -o install.sh https://raw.githubusercontent.com/Quad4-Software/Athenaeum/master/install.sh
chmod +x install.sh
./install.sh # interactive
./install.sh --dry-run # print actions only
./install.sh --method docker --port 8080 --library /media/books -y
./install.sh --method binary --service systemd --ip 127.0.0.1
./install.sh --method source --prefix /usr/local --no-service
./install.sh --no-color
OptionDescription
--methodbinary, docker, or source (prompted if omitted)
--ip / --portListen address
--library / --dataLibrary root and data directory
--prefixBinary install prefix (default /usr/local)
--servicesystemd, openrc, runit, dinit, s6, or none
--user <name>System user (default athenaeum)
--no-userRun the service as the invoking user
--no-serviceSkip service unit installation
--version / --repo / --release-baseRelease asset selection
--image / --composeDocker image and compose file
--admin-user / --admin-passOptional first-admin bootstrap
--dry-run / -yPreview actions / assume yes
--color / --no-colorForce or disable ANSI color (-h shows full help)

Failed installs roll back tracked filesystem and service changes. Use --method source when no release assets are published yet.

Service unit templates live under deploy/:

InitPath
systemddeploy/systemd/athenaeum.service
OpenRCdeploy/openrc/athenaeum
runitdeploy/runit/athenaeum/
dinitdeploy/dinit/athenaeum
s6deploy/s6/athenaeum/

Shared environment example: deploy/env/athenaeum.env.example.

Docker​

Published images are multi-arch on GHCR (ghcr.io/quad4-software/athenaeum): linux/amd64 and linux/arm64. latest publishes on every push to master. Version tags publish on v* releases. Release binaries also cover armv6, armv7, and riscv64 (and BSD) outside the container.

docker run​

docker run -d --name athenaeum \
-p 8080:8080 \
-v athenaeum-data:/data \
-v /path/to/books:/library \
ghcr.io/quad4-software/athenaeum:latest

Compose​

Copy .env.example to .env and set optional bootstrap credentials. Point ATHENAEUM_LIBRARY_HOST_PATH at your media folder (default: ./library).

git clone https://github.com/Quad4-Software/Athenaeum.git
cd Athenaeum
cp .env.example .env
docker compose up -d

docker compose up -d pulls GHCR by default. Use docker compose up -d --build for a local image.

Open http://localhost:8080 (or the host port from ATHENAEUM_PUBLISH_PORT). The container always uses /data for the database and /library for the initial scan mount. Host paths in ATHENAEUM_DATA / ATHENAEUM_LIBRARY apply to local runs only, not Docker.

Additional library mounts (local paths inside the container, or MinIO-compatible S3 backends) are configured in Settings after first login. S3 credentials are stored in the database per mount, not as compose env vars.

VariableDefaultDescription
ATHENAEUM_LIBRARY_HOST_PATH./libraryHost folder bind-mounted to /library
ATHENAEUM_PUBLISH_PORT8080Host port published to container :8080
ATHENAEUM_IMAGEghcr.io/quad4-software/athenaeum:<version>Image to pull (tag from ATHENAEUM_VERSION)
ATHENAEUM_VERSIONlatestImage tag (Coolify compose defaults to 0.1.0)
ATHENAEUM_REVISION / ATHENAEUM_CREATEDImage build labels for local builds
ATHENAEUM_ADMIN_USER / ATHENAEUM_ADMIN_PASSOptional first-admin bootstrap via .env
ATHENAEUM_SENTRY_DSNOptional Sentry/GlitchTip DSN (passed through env_file)
ATHENAEUM_ALTCHA_ENABLEDfalseOptional ALTCHA PoW on login/setup

Optional sidecars use Compose profiles on the same file (combine freely, or set COMPOSE_PROFILES in .env):

docker compose --profile altcha up -d --build
docker compose --profile kokoro up -d --build
docker compose --profile postgres up -d --build
  • altcha: ALTCHA Sentinel backend (trial/license). Set Athenaeum to ATHENAEUM_ALTCHA_MODE=sentinel and point challenge/verify URLs at the Sentinel service. See ALTCHA docs.
  • kokoro: Optional Kokoro TTS sidecar for server-side EPUB narration and whole-book audiobook generation. In-browser Kokoro (and browser SpeechSynthesis) work without this profile. In Settings -> Administration -> Narration (TTS) set base URL to http://kokoro:8880 (same Compose network) or http://127.0.0.1:8880 from the host. First start downloads model weights and may take a few minutes. The sidecar speaks the OpenAI /v1/audio/speech shape, so any compatible endpoint works instead: Kokoro-FastAPI, Speaches, or a hosted API. Audiobook generation always requests MP3 output; the endpoint must be able to produce it (the bundled image encodes with ffmpeg). Generated books land in audiobooks/ inside the library and appear after the next scan.
  • postgres: Postgres 16 instead of SQLite. Also set in .env: ATHENAEUM_DATABASE_DRIVER=postgres and ATHENAEUM_DATABASE_URL=postgres://athenaeum:athenaeum@postgres:5432/athenaeum?sslmode=disable. Back up with pg_dump (the admin ZIP backup skips the SQLite file and notes that an external dump is required).

Coolify deployments should use docker-compose.coolify.yml (same profiles).

Persistent state lives in the athenaeum-data Docker volume. Back it up with ./scripts/backup.sh --docker (see Backup below) or:

docker run --rm -v "${COMPOSE_PROJECT_NAME:-$(basename "$PWD")}_athenaeum-data":/data -v "$PWD":/backup alpine tar czf /backup/athenaeum-data.tgz -C /data .

Put a reverse proxy in front for TLS and forward X-Forwarded-For for audit logs.

ALTCHA setup details are in Authentication.

Host services​

Prefer the units under deploy/ plus /etc/athenaeum/athenaeum.env. Example systemd unit (also installed by ./install.sh --service systemd):

[Unit]
Description=Athenaeum media library server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=athenaeum
Group=athenaeum
EnvironmentFile=-/etc/athenaeum/athenaeum.env
WorkingDirectory=/var/lib/athenaeum
ExecStart=/usr/local/bin/athenaeum
Restart=on-failure

[Install]
WantedBy=multi-user.target

Put a reverse proxy (Caddy, nginx, Traefik) in front for TLS. Forward X-Forwarded-For so audit logs record client IPs correctly.

To serve a frontend build from disk instead of the binary-embedded assets, set ATHENAEUM_WEB_DIR in the env file (or pass --web-dir). The directory must contain the Vite production output (index.html and assets/).

Backup​

Back up the entire --data directory. With the default SQLite driver it contains the database file and cached cover images. When using PostgreSQL, also run pg_dump against ATHENAEUM_DATABASE_URL. Library files on disk are not modified by Athenaeum.

./scripts/backup.sh -d /var/lib/athenaeum/data -o ./backups/data.tar.gz
./scripts/backup.sh --include-library -l /media/books -o ./backups/full.tar.gz
./scripts/backup.sh --docker -o ./backups/docker-data.tar.gz
./scripts/restore.sh --from ./backups/data.tar.gz -d /var/lib/athenaeum/data

backup.sh uses sqlite3 .backup when available for a consistent database snapshot. Stop the service before restore, then restart it.

Verifying downloads and hardening the container​

Release binaries, checksums, and the GHCR image are signed keylessly by the release workflows and carry GitHub build provenance and SBOM attestations. See SECURITY.md in the repository for the exact cosign verify, cosign verify-blob, and gh attestation verify commands, the certificate identity patterns for each workflow, and hardened docker run / podman run examples (read-only root filesystem, dropped capabilities, no-new-privileges).

The image runs as a fixed non-root user (uid/gid 10001) and writes only to /data and /library. The bundled compose files already set read_only: true, a /tmp tmpfs, cap_drop: [ALL], and no-new-privileges, and the systemd unit in deploy/systemd/ applies standard sandboxing directives.