Configuration
The docker-compose.yml file controls which containers FediSuite starts, how they communicate with each other, where data is stored, and what happens when a container crashes. This page explains every part of it — even if you've never worked with Docker before.
On this page
What is the docker-compose.yml?
Think of the docker-compose.yml as a blueprint. It defines which programs (containers) get started, how they can reach each other, where they store their data, and in which order they should start.
A single Docker container is like a program running in a completely isolated environment — it has its own file system, its own network interface, and runs independently from the rest of the system. Docker Compose orchestrates multiple such containers and lets them work together.
You do not need to touch the docker-compose.yml for basic FediSuite operation. All personal settings like domain, passwords, and email configuration belong in the .env file. Only those who want to customize the worker setup or integrate Traefik need to edit this file.
Overview: the four services
In full operation, FediSuite consists of four containers. Each has a clearly defined role:
db
postgres:15-alpine
The database. Permanently stores all users, posts, account links, settings, and history data.
app
christinloehner/fedisuite:latest
The core. Provides the web interface and API through which users operate FediSuite. Automatically initializes the database on first start.
worker1
christinloehner/fedisuite:latest
Handles all background tasks: publishes scheduled posts on time, fetches updated stats from platforms, sends reminders, and generates tips. You can run more than one worker.
worker2, worker3 …
christinloehner/fedisuite:latest
Each additional worker shares the load so stats stay fresh as your instance grows. app.fedisuite.com, for example, runs 5 workers.
Service: db (PostgreSQL)
db:
image: postgres:15-alpine
env_file:
- .env
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql/data
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" ]
interval: 5s
timeout: 5s
retries: 5
image: postgres:15-alpine
The official PostgreSQL image in version 15, based on Alpine Linux — a particularly lightweight Linux variant. FediSuite uses PostgreSQL as its database; MySQL or other systems are not supported.
env_file: .env
The database container reads POSTGRES_DB, POSTGRES_USER, and POSTGRES_PASSWORD directly from your .env file. You don't need to enter these values twice.
restart: unless-stopped
If the container crashes or the server restarts, Docker automatically brings it back up — unless you deliberately stopped it with docker compose stop. This ensures FediSuite keeps running reliably after a reboot without any manual intervention.
volumes: ./postgres:/var/lib/postgresql/data
Something important happens here: the ./postgres folder on your server is linked to the /var/lib/postgresql/data folder inside the container. This is called a Bind Mount.
Without this volume, all database data would be lost as soon as the container is deleted or recreated — for example, during an update. With the bind mount, the data remains on your server regardless of what happens to the container.
./postgres/ directory is created automatically next to your docker-compose.yml on first start. Never delete this folder — it contains all your data.
healthcheck
Docker checks every 5 seconds whether PostgreSQL is actually ready to accept connections — not just whether the container is running. Only once this check passes (after at most 5 attempts) does the app container start. This prevents the app from trying to access a database that hasn't finished starting up yet.
Service: app
app:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
pull_policy: always
env_file:
- .env
environment:
- ENABLE_SCHEDULER=false
- ENABLE_POSTS_REFRESH=false
- ENABLE_IDLE_REMINDER=false
- ENABLE_TIPS_ENGINE=false
depends_on:
db:
condition: service_healthy
volumes:
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./logs:/app/logs
ports:
- "3000:3000"
restart: unless-stopped
command: sh -c "node server/init-db.js && node server/index.js"
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
Uses the image you set in FEDISUITE_IMAGE in your .env. The part after :- is the default value if the variable is empty — in this case christinloehner/fedisuite:latest.
pull_policy: always
Docker checks on every start whether a newer version of the image is available on Docker Hub and downloads it if needed. This ensures that after a docker compose up -d you're always running the latest version — as long as you use the latest tag.
environment: ENABLE_*=false
In full operation, the app container has all background jobs disabled — the workers handle those tasks. The difference between env_file and environment:
- •
env_fileloads all variables from the.envfile into the container. - •
environmentsets individual variables directly indocker-compose.yml— and overrides values from the.env. This is intentional: the workers should handle the jobs, not the app.
depends_on: db: condition: service_healthy
The app waits to start until the db container passes its health check — meaning PostgreSQL is actually accepting connections. Without this dependency, the app could try to initialize the database before it's ready, causing startup errors.
ports: "3000:3000"
The FediSuite app runs internally on port 3000. The mapping 3000:3000 makes this port accessible on the host system — the server itself. If you use Traefik as a reverse proxy, Traefik can reach the app through this port, and you access FediSuite at your domain via HTTPS.
volumes: ./uploads, ./plugins & ./logs
Three bind mounts:
- •
./uploads:/app/uploads— FediSuite stores files uploaded by users here (e.g. media for posts). These are preserved across updates. - •
./plugins:/app/plugins— The plugins folder. Plugins are managed on the host and accessible to the app and workers. - •
./logs:/app/logs— Application audit logs. FediSuite writes security-relevant events (login, registration, 2FA, etc.) as JSON Lines to monthly-rotating files (audit-YYYY-MM.log). Logs do not appear in container stdout.
command: node server/init-db.js && node server/index.js
When the container starts, two steps run in sequence: first, init-db.js initializes the database structure — creates tables, sets up the first admin account from ADMIN_EMAIL/ADMIN_PASSWORD. Then index.js starts the actual app. You don't need to trigger this initialization manually.
Workers: background tasks
Think of FediSuite like a small office: the app container is the front desk — it takes requests from users and responds immediately. Workers are the back-office staff who handle everything that doesn't need to happen instantly but needs to be done regularly.
Specifically, workers handle these tasks automatically in the background — without users or admins having to do anything:
Publish scheduled posts
When a user schedules a post for 2:00 PM, the worker makes sure it goes out on Mastodon, Bluesky & co. right on time.
Update statistics
Likes, reposts, comments — the worker regularly fetches fresh numbers from the platforms so the analytics dashboard stays current.
Send inactivity reminders
If a user has posting goals enabled and hasn't posted in a while, the worker automatically sends a friendly nudge.
Generate tips
The tips engine analyses which posts performed particularly well and prepares helpful suggestions for users in the background.
app container isn't handling the jobs itself, scheduled posts will not be published, statistics will stay frozen, and reminders will not be sent. Either the app container handles these tasks itself (minimal setup, see below) — or you run at least one worker.
How many workers do I need?
It depends on how many users your instance has. As a rule of thumb:
Just you or 2–3 people
→ No worker neededRun all background jobs directly inside the app container (minimal setup). Saves resources.
Small group (up to ~20 users)
→ 1 workerOne worker reliably handles all tasks, leaving the app container free for the web interface.
Medium community (up to ~100 users)
→ 2 workersWorker1 handles scheduling and reminders; worker2 helps with statistics and tips.
Large community (100+ users)
→ 3–5 workersapp.fedisuite.com runs 5 workers to serve thousands of accounts smoothly. More workers = faster stats updates.
Configuring workers
All workers use the same Docker image as the app container — but without a web interface. Task distribution is controlled via environment variables. Key rule: ENABLE_SCHEDULER=true must appear in exactly one worker — otherwise scheduled posts may be published multiple times.
worker1:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
pull_policy: always
env_file:
- .env
environment:
- ENABLE_SCHEDULER=true # Only true here — publishes scheduled posts
- ENABLE_POSTS_REFRESH=true # Fetch stats from platforms
- ENABLE_IDLE_REMINDER=true # Send inactivity reminders
- ENABLE_TIPS_ENGINE=true # Generate tips for users
- WORKER_ID=worker-1
- REFRESH_BATCH_SIZE=3
depends_on:
db:
condition: service_healthy
volumes:
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./logs:/app/logs
restart: unless-stopped
command: sh -c "node server/index.js"
worker2:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
pull_policy: always
env_file:
- .env
environment:
- ENABLE_SCHEDULER=false # false — scheduler only runs in worker1
- ENABLE_POSTS_REFRESH=true # Helps with stats updates
- ENABLE_IDLE_REMINDER=false # Reminders are fine in one worker
- ENABLE_TIPS_ENGINE=true # Process tips in parallel
- WORKER_ID=worker-2
- REFRESH_BATCH_SIZE=3
depends_on:
db:
condition: service_healthy
volumes:
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./logs:/app/logs
restart: unless-stopped
command: sh -c "node server/index.js"
Adding more workers (worker3, worker4 …)
Simply copy the worker2 block and increment the number in WORKER_ID and the service name. All additional workers keep ENABLE_SCHEDULER=false.
worker3:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
pull_policy: always
env_file:
- .env
environment:
- ENABLE_SCHEDULER=false
- ENABLE_POSTS_REFRESH=true
- ENABLE_IDLE_REMINDER=false
- ENABLE_TIPS_ENGINE=true
- WORKER_ID=worker-3
- REFRESH_BATCH_SIZE=3
depends_on:
db:
condition: service_healthy
volumes:
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./logs:/app/logs
restart: unless-stopped
command: sh -c "node server/index.js"
What do the variables mean in detail?
ENABLE_SCHEDULER
Controls whether this container publishes scheduled posts at the right time. Must be true in exactly one container — otherwise posts are either never published or sent multiple times. In normal operation with workers: true in worker1, false in all others.
ENABLE_POSTS_REFRESH
Regularly fetches updated numbers from Fediverse platforms: likes, reposts, comments, reach. Can run in multiple containers at the same time — the work is split between them and completes faster. How many posts are processed per cycle is set by REFRESH_BATCH_SIZE.
ENABLE_IDLE_REMINDER
Regularly checks whether users have posting goals enabled and haven't posted in a while — and then automatically sends a reminder. One container is enough for this.
ENABLE_TIPS_ENGINE
Analyses in the background which posts performed particularly well or poorly and turns that into tips for users. Can run in multiple containers.
WORKER_ID
A freely chosen name for this container, e.g. worker-1, worker-2. It appears in the audit logs and helps you trace which worker did what when troubleshooting. Every worker needs its own unique ID.
REFRESH_BATCH_SIZE
How many posts the worker updates from platforms in one cycle. Small value (e.g. 3): gentler on platform APIs but slower with many users. Larger value (e.g. 10): faster but more API requests. Recommendation: 3 is a solid starting point — increase if needed.
Minimal setup: only db + app
Workers are optional. If you run FediSuite for yourself or a very small group, you can skip separate worker containers entirely. This saves RAM and system resources since fewer containers are running.
In the default setup the app container has all background jobs disabled (ENABLE_*=false), because normally workers handle those tasks. In minimal setup you simply turn them back on in the app container:
app:
image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
...
environment:
- ENABLE_SCHEDULER=true # ← set to true
- ENABLE_POSTS_REFRESH=true # ← set to true
- ENABLE_IDLE_REMINDER=true # ← set to true
- ENABLE_TIPS_ENGINE=true # ← set to true
Then delete or comment out all worker blocks from your docker-compose.yml. You then simply start with docker compose up -d — only db and app will run.
docker-compose.yml, set the ENABLE_* flags in the app container back to false, and run docker compose up -d. No data is lost in the process.
Volumes: where data is stored
Containers are inherently ephemeral — when a container is removed, all data inside it is gone. Volumes solve this problem: they link a folder on the host system with a folder inside the container so that data persists permanently.
FediSuite uses exclusively Bind Mounts — meaning you can see the data directly as regular folders next to the docker-compose.yml. This makes backups straightforward: just back up these folders.
./postgres/
→
/var/lib/postgresql/data
Used by: db
All database data: user accounts, posts, settings, Fediverse account links. The most important part of the entire setup.
./uploads/
→
/app/uploads
Used by: app, worker1, worker2
Files uploaded by users, e.g. images for posts. Must be shared between the app and workers.
./plugins/
→
/app/plugins
Used by: app, worker1, worker2
Installed plugins. Plugins are managed on the host and accessible to the app and workers.
./logs/
→
/app/logs
Used by: app, worker1, worker2
Application audit logs. FediSuite writes security-relevant events as JSON Lines to monthly-rotating files (audit-YYYY-MM.log). Logs do not appear in container stdout and contain no personal data in plain text (GDPR-compliant).
Further concepts explained
Why do worker1 and worker2 use the same image as app?
FediSuite is a single Node.js application that takes on different roles depending on environment variables. The app image can start as both a web server and a background processor. The role a container takes on is determined by the combination of ENABLE_* variables and the command entry.
What does depends_on with service_healthy mean?
Without this condition, Docker would start all containers simultaneously. The app could then try to access the database before PostgreSQL is even ready — leading to startup errors. With condition: service_healthy, each dependent container waits until the db health check has passed successfully.
Networking: how do containers communicate?
Docker Compose automatically creates an internal network in which all containers in the stack can reach each other by their service name. The app talks to the database simply using the hostname db — that's why the DATABASE_URL reads postgresql://...@db:5432/.... The database is not accessible from the outside.
Traefik labels in the docker-compose.yml
In the app service you'll find commented-out Traefik labels. These tell Traefik which domain the app should be accessible at and which cert resolver to use for TLS. How to set up Traefik and customize these labels is covered on the next pages.