# Deploying Vellum Vellum is a single Go binary in a single container with a single SQLite database. This document covers a container deployment with `compose.yml`. ## Prerequisites - **Podman or Docker** with the Compose plugin (`podman compose` or `docker compose`). - **A media folder** on the host, laid out one directory per media kind (`ebooks/`, `comics/`, `audiobooks/`, `music/`, `photos/`, `games/`). Vellum scans these; it never writes to them. - A host directory for the database and cache (e.g. `/opt/mediaserver/vellum/{data,config}`). The image is built locally with `make docker` (Podman) or `docker build -t localhost/vellum:0.1.0-dev .`. ## Configuration: `.env` Every setting is an environment variable with a working default, so Vellum starts with no configuration at all. Copy the relevant values into a `.env` next to `compose.yml`: ```dotenv # --- Media roots (colon-separated, read-only) ------------------------------- VELLUM_MEDIA_DIRS=/media/ebooks:/media/comics:/media/audiobooks:/media/music:/media/photos:/media/games # --- Server ----------------------------------------------------------------- VELLUM_LISTEN=:7788 VELLUM_LOG_LEVEL=info # --- First admin (only used on an EMPTY database) --------------------------- VELLUM_ADMIN_USER=admin VELLUM_ADMIN_PASSWORD=change-me-at-least-8-chars # --- Optional metadata providers -------------------------------------------- VELLUM_COMICVINE_API_KEY= VELLUM_IGDB_CLIENT_ID= VELLUM_IGDB_CLIENT_SECRET= # --- Set to true once you serve over TLS ------------------------------------ VELLUM_COOKIE_SECURE=false ``` The same names are what the process reads directly; `compose.yml` passes them through. Defaults, for reference: `VELLUM_LISTEN=:7788`, `VELLUM_LOG_LEVEL=info`, `VELLUM_COOKIE_SECURE=false`. ## Volumes and SELinux `compose.yml` uses three kinds of mount: | Mount | Mode | Purpose | | --- | --- | --- | | `/data` | read/write | SQLite database (`vellum.db`) and persistent state | | `/config` | read/write | Regenerable cache: covers, thumbnails, extracted pages | | `/media/` | **read-only** | The media library | Each media kind is bind-mounted individually and read-only. **Vellum never writes to `/media`.** If you add a media root, add a matching read-only mount and a matching entry in `VELLUM_MEDIA_DIRS`. Every bind mount carries the `:z` suffix, which is **mandatory on SELinux hosts** (this stack runs with `getenforce = Enforcing`). Without `:z` the container cannot read the host paths and Vellum fails to start with permission errors. `:z` relabels the files so the container's SELinux context may access them; `:ro` keeps the media read-only. ## First admin bootstrap Set `VELLUM_ADMIN_USER` and `VELLUM_ADMIN_PASSWORD` (minimum 8 characters) before the first start. On boot, if the users table is empty, Vellum creates that administrator. This happens **only on an empty database**. Setting these on an established install does nothing — the credentials are ignored. If the database is empty and both are unset, the server logs a warning and refuses to invent a default password. Once your admin exists, remove the two variables from `.env`. ## Reverse proxy and TLS Vellum speaks plain HTTP on `:7788`; `compose.yml` binds it to `127.0.0.1:7788` so it is only reachable from the host. Put a reverse proxy (nginx, Caddy, Traefik) in front of it to terminate TLS and forward to `http://127.0.0.1:7788`. Once traffic arrives over HTTPS, set: ```dotenv VELLUM_COOKIE_SECURE=true ``` This marks session cookies `Secure`, so browsers only send them over TLS. When it is `false` Vellum logs a warning at boot because session cookies can be sent over plain HTTP. Restart the container after changing it. ## Backup The entire persistent state is the SQLite database at `/data/vellum.db` (in WAL mode). Two safe options: - **Stop the container, then copy the database.** ```bash podman compose stop vellum cp /opt/mediaserver/vellum/data/vellum.db /backup/vellum-$(date +%F).db podman compose start vellum ``` - **Take a consistent snapshot while running** with SQLite's `VACUUM INTO`: ```bash podman exec vellum \ sqlite3 /data/vellum.db "VACUUM INTO '/data/backup.db'" ``` `VACUUM INTO` writes a single consistent file without stopping the server; copy that file off the volume when it is done. `/config` holds only regenerable cache and does not need backing up. ## Upgrading 1. **Back up first** (see above). Migrations are one-way. 2. Pull or rebuild the image, then recreate the container: ```bash make docker podman compose up -d ``` **Migrations run automatically at boot.** They are embedded and applied in filename order, recorded by number in `PRAGMA user_version`. Never edit an applied migration; a new schema change is always a new file. If a migration fails, the process exits and the container restarts — restore your backup. ## Logs Vellum logs to **stdout/stderr**, so container logs are the log. With the `json-file` driver configured in `compose.yml` (10 MB per file, 3 files): ```bash podman logs -f vellum # follow podman logs --tail 100 vellum # last 100 lines ``` Verbosity is controlled by `VELLUM_LOG_LEVEL` (`debug`, `info`, `warn`, `error`). Set `VELLUM_LOG_FORMAT=json` for structured output if you ship logs to a collector.