๐ณ 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:
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.
๐๏ธ 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 theDockerfilein the project root. - ๐
ports: Maps the host port (${PORT:-8000}) to the container's port8000, and${TEST_PORT:-8001}to8001for test mode. - ๐
volumes: A bind mount./LibreFolio-dataโ/app/backend/data/prod-dockerpersists database, uploads, broker reports, and logs in the same directory asdocker-compose.yml. - ๐
env_file: .env: Loads all configuration from the.envfile (copied from.env.example). - ๐
environment: Overrides only Docker-specific values:LIBREFOLIO_DATA_DIR(container path) andHOST=0.0.0.0. - ๐ฉบ
healthcheck: PollsGET /api/v1/system/healthevery 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:
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:
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:
Add your existing user to a group:
Verify group membership:
Set ownership of the data directory:
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:
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
-
Start the container (production server starts automatically on
:8000): -
Populate the test database with mock data:
-
Start the test server on port 8001:
-
Access at
http://localhost:8001Test credentials:
Username Password e2e_test_userE2eTestPass123!e2e_test_adminE2eAdminPass123!
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:
๐ญ 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:
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 |