Skip to content

๐Ÿณ Advanced Docker Guide

This guide provides a deeper look into the Docker configuration for LibreFolio, intended for users who want to customize their deployment.

โš ๏ธ Prerequisites

Docker group (Linux)

On Linux, your user must be in the docker group to run Docker commands without sudo:

sudo usermod -aG docker $USER

Then log out and log back in, or run newgrp docker to activate the group in the current session. Without this, all docker and docker compose commands will fail with a permission error.

.env file required

LibreFolio requires a .env file in the project root. If it's missing, ./dev.py docker build will refuse to proceed.

cp .env.example .env
$EDITOR .env          # review and customize parameters

๐Ÿ—๏ธ Architecture

LibreFolio uses a runtime-only Docker image. The frontend (SvelteKit) and documentation (MkDocs) are built on the host and then copied into the image. The ./dev.py docker build command handles this automatically.

Host (build)                    Docker Image (runtime)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ frontend/src โ”‚โ”€โ”€npm buildโ”€โ”€โ–ถ  โ”‚ frontend/build/      โ”‚
โ”‚ mkdocs_src/  โ”‚โ”€โ”€mkdocs โ”€โ”€โ”€โ–ถ   โ”‚ mkdocs_src/site/     โ”‚
โ”‚ backend/     โ”‚โ”€โ”€copyโ”€โ”€โ”€โ”€โ”€โ”€โ–ถ   โ”‚ backend/             โ”‚
โ”‚ Pipfile*     โ”‚โ”€โ”€pipenv โ”€โ”€โ”€โ–ถ   โ”‚ Python packages      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“„ docker-compose.yml

The docker-compose.yml file defines the service and persistent data directory.

๐Ÿ”ง Service: librefolio

  • ๐Ÿ—๏ธ build: .: Builds from the Dockerfile in the project root.
  • ๐Ÿ”Œ ports: Maps the host port (${PORT:-8000}) to the container's port 8000, and ${TEST_PORT:-8001} to 8001 for test mode.
  • ๐Ÿ“‚ volumes: A bind mount ./LibreFolio-data โ†’ /app/backend/data/prod-docker persists database, uploads, broker reports, and logs in the same directory as docker-compose.yml.
  • ๐Ÿ“ env_file: .env: Loads all configuration from the .env file (copied from .env.example).
  • ๐ŸŒ environment: Overrides only Docker-specific values: LIBREFOLIO_DATA_DIR (container path) and HOST=0.0.0.0.
  • ๐Ÿฉบ healthcheck: Polls GET /api/v1/system/health every 30 seconds.

๐Ÿ’พ Data Directory: LibreFolio-data/

A bind mount directory created alongside docker-compose.yml. Contains the SQLite database, custom uploads, broker reports, and log files. Data survives container stop/restart/removal. You can back it up directly from the host filesystem.

๐Ÿ‘ค User & Permissions

The LibreFolio container runs as a non-root user for security. The default UID/GID is 1000:1000. Files created by the application in LibreFolio-data/ will be owned by this UID/GID on the host.

Choosing the right UID and GID

Set UID and GID in your .env file to match the host user (or dedicated user) that should own the data files:

UID=1000
GID=1000

How ls -l shows ownership

On the host, ls -l LibreFolio-data/ shows your chosen user/group name (resolved from UID/GID via /etc/passwd).

Inside the container, the same files show as librefolio:librefolio โ€” it's the same numeric UID/GID, just resolved against the container's own /etc/passwd.

Linux cheatsheet: users, groups, and IDs

Discover your current UID and GID:

id -u              # your user ID (e.g. 1000)
id -g              # your primary group ID (e.g. 1000)
id                 # full info: uid, gid, groups

Find the UID/GID of any user:

id -u username     # UID of 'username'
id -g username     # primary GID of 'username'

Create a new group:

sudo groupadd librefolio          # create group (auto-assigns GID)
sudo groupadd -g 1500 librefolio  # create group with specific GID

Create a new user:

# System user (no home, no login โ€” ideal for services)
sudo useradd --system --no-create-home --gid librefolio --shell /usr/sbin/nologin librefolio

# Regular user with home directory
sudo useradd -m -g librefolio librefolio

Check the assigned IDs:

id librefolio
# โ†’ uid=998(librefolio) gid=998(librefolio) groups=998(librefolio)

Add your existing user to a group:

sudo usermod -aG librefolio $USER
newgrp librefolio    # activate in current session (or log out/in)

Verify group membership:

groups $USER         # list all groups for your user

Set ownership of the data directory:

sudo chown -R librefolio:librefolio ./LibreFolio-data

Then set the matching UID/GID in .env.

๐Ÿ› ๏ธ CLI Commands

All Docker operations are available through dev.py:

./dev.py docker build          # Build image (auto-builds frontend + docs)
./dev.py docker build --no-cache  # Full rebuild without Docker cache
./dev.py docker rebuild        # Build โ†’ stop โ†’ restart (one-step deploy)
./dev.py docker up             # Start containers
./dev.py docker down           # Stop containers
./dev.py docker logs -f        # Follow container logs
./dev.py docker status         # Show container status
./dev.py docker exec <cmd>     # Run a dev.py command inside the container

Documentation with screenshots

If you are building the documentation and want complete screenshots in the gallery, run:

./dev.py mkdocs --gallery

This requires a fully installed environment (with pipenv) and a running server with populated test data. Be patient โ€” gallery generation takes a few minutes.

๐Ÿ“ก docker exec โ€” Running Commands Inside the Container

The docker exec subcommand forwards any dev.py command into the running container:

./dev.py docker exec user create admin admin@example.com Pass123!
./dev.py docker exec user list
./dev.py docker exec db upgrade
./dev.py docker exec server --test

This is equivalent to running docker compose exec librefolio python dev.py <cmd>.

๐Ÿงช Test Mode

The Docker Compose configuration exposes two ports:

Port Purpose Database
8000 Production server (started by container CMD) prod-docker/sqlite/app.db (persistent volume)
8001 Test server (started manually via docker exec) test/sqlite/app.db (ephemeral)

Starting the Test Server

  1. Start the container (production server starts automatically on :8000):

    docker compose up -d
    
  2. Populate the test database with mock data:

    ./dev.py docker exec test db populate --force --with-static
    
  3. Start the test server on port 8001:

    ./dev.py docker exec server --test
    
  4. Access at http://localhost:8001

    Test credentials:

    Username Password
    e2e_test_user E2eTestPass123!
    e2e_test_admin E2eAdminPass123!

Test data is ephemeral

The test database lives inside the container's writable layer, not on a persistent bind mount. This means:

  • โœ… Data survives docker compose stop / docker compose start (container is paused, not removed).
  • โŒ Data is lost with docker compose down (container is removed and recreated).

If you need persistent test data, add a dedicated bind mount in docker-compose.yml:

volumes:
  - ./LibreFolio-data:/app/backend/data/prod-docker
  - ./LibreFolio-test-data:/app/backend/data/test    # โ† add this

๐Ÿญ Production Considerations

๐ŸŽฎ 1. Customizing docker-compose.yml

The repository includes a ready-to-use docker-compose.yml. Here is the full file with annotations showing what you can customize:

services:
  librefolio:
    image: librefolio:latest           # Built by ./dev.py docker build
    build:
      context: .
      args:
        UID: ${UID:-1000}              # (1) Match host user UID
        GID: ${GID:-1000}              # (1) Match host user GID
    container_name: librefolio
    # No 'user:' directive โ€” entrypoint starts as root, fixes permissions,
    # then drops to 'librefolio' user via gosu (same pattern as postgres/redis).
    restart: unless-stopped
    ports:
      - "${PORT:-8000}:8000"           # (2) Production port โ€” change via PORT in .env
      - "${TEST_PORT:-8001}:8001"      # (3) Test server port (optional)
    volumes:
      - ./LibreFolio-data:/app/backend/data/prod-docker  # (4) Persistent data (bind mount)
    env_file: .env                     # (5) All config from .env file
    environment:
      - LIBREFOLIO_DATA_DIR=/app/backend/data/prod-docker  # Docker-specific override
      - HOST=0.0.0.0
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/v1/system/health')"]
      interval: 30s
      timeout: 10s
      start_period: 15s
      retries: 3

Common customizations:

# What How
(1) Match host UID/GID Set UID=1001 and GID=1001 in .env, then rebuild
(2) Change production port Set PORT=3000 in .env
(3) Disable test port Remove the TEST_PORT line from ports:
(4) Custom data path Change bind mount: ./my-data:/app/backend/data/prod-docker
(5) All configuration Edit .env file (copied from .env.example)

First user

The first time you access LibreFolio in the browser, you'll see a registration page. Create your account directly โ€” the first user automatically becomes the administrator. No CLI needed.

๐Ÿ”’ 2. Reverse Proxy

It is highly recommended to run LibreFolio behind a reverse proxy like Nginx or Traefik. This allows you to:

  • ๐Ÿ” Easily manage SSL/TLS certificates for HTTPS.
  • ๐Ÿ–ฅ๏ธ Serve multiple applications on the same server.
  • ๐Ÿ›ก๏ธ Add security headers and rate limiting.

๐Ÿ’พ 3. Database Backup

The database is stored in the LibreFolio-data/ directory alongside docker-compose.yml. Back it up directly from the host filesystem:

#!/bin/bash
cp ./LibreFolio-data/sqlite/app.db /path/to/backups/app.db-$(date +%F)

No docker cp needed โ€” the data directory is a bind mount accessible from the host.

๐Ÿ”‘ 4. Environment Variables

All configuration is managed in the .env file (copied from .env.example). The Docker-specific overrides in the environment: block should not be changed:

Variable Default Description Where
PORT 8000 Host port for production server .env
TEST_PORT 8001 Host port for test server .env
UID 1000 Container user UID (must match data directory owner) .env
GID 1000 Container user GID (must match data directory owner) .env
LOG_LEVEL INFO Logging verbosity (DEBUG, INFO, WARNING, ERROR) .env
PORTFOLIO_BASE_CURRENCY EUR Base currency for portfolio calculations .env
PREVIEW_CACHE_MAX_MB 50 Max in-memory image preview cache (MB) .env
LIBREFOLIO_DATA_DIR /app/backend/data/prod-docker Container path for data (do not change) docker-compose.yml
HOST 0.0.0.0 Container bind address (do not change) docker-compose.yml