Installation

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.

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
Required

The database. Permanently stores all users, posts, account links, settings, and history data.

app christinloehner/fedisuite:latest
Required

The core. Provides the web interface and API through which users operate FediSuite. Automatically initializes the database on first start.

worker1 christinloehner/fedisuite:latest
Optional

Background jobs: executes scheduled posts, updates analytics data, sends reminders, and generates tips.

worker2 christinloehner/fedisuite:latest
Optional

Additional worker for post refresh and the tips engine. Offloads worker1 when many users are active simultaneously.

Important: None of the four containers use Redis. Background jobs are managed directly by the worker processes — no separate cache service is needed.

Service: db (PostgreSQL)

docker-compose.yml
  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.

Remember: The ./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

docker-compose.yml
  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
    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_file loads all variables from the .env file into the container.
  • environment sets individual variables directly in docker-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

Two 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, mounted as read-only (:ro). The app can read plugins but not write to them. Plugins are managed on the host.

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.

Services: worker1 & worker2

The workers use the same image as app, but with a different configuration: they don't start a web interface, only the background processor. What they actually do is controlled via the ENABLE_* variables.

docker-compose.yml — worker1
    environment:
      - ENABLE_SCHEDULER=true       # Execute scheduled posts
      - ENABLE_POSTS_REFRESH=true   # Update analytics data
      - ENABLE_IDLE_REMINDER=true   # Send inactivity reminders
      - ENABLE_TIPS_ENGINE=true     # Generate tips for users
      - WORKER_ID=worker-1
      - REFRESH_BATCH_SIZE=5
docker-compose.yml — worker2
    environment:
      - ENABLE_SCHEDULER=false      # Scheduler runs only in worker1
      - ENABLE_POSTS_REFRESH=true
      - ENABLE_IDLE_REMINDER=false
      - ENABLE_TIPS_ENGINE=true
      - WORKER_ID=worker-2
      - REFRESH_BATCH_SIZE=5
ENABLE_SCHEDULER

Ensures that scheduled posts are published at the right time. Should only run in one worker, as running it in multiple workers could cause posts to be sent twice.

ENABLE_POSTS_REFRESH

Regularly fetches updated data from Fediverse platforms — e.g. new reactions on already published posts. Can run in multiple workers; REFRESH_BATCH_SIZE controls how many posts are processed per cycle.

ENABLE_IDLE_REMINDER

Sends reminders to users who haven't posted for a while. Running in one worker is sufficient.

ENABLE_TIPS_ENGINE

Generates tips and suggestions for users. Can be distributed across multiple workers.

WORKER_ID

A unique ID for the worker — helps with log tracing when multiple workers are running.

REFRESH_BATCH_SIZE

How many posts the worker processes per refresh cycle. Lower values are gentler on Fediverse APIs; higher values speed up updates when many users are active.

Minimal setup: only db + app

Workers are optional. If you run FediSuite for yourself or a very small group, db and app are perfectly sufficient. This saves RAM and system resources.

Since the app container in the default setup has all ENABLE_* flags set to false, you'd need to set these to true in the app service for minimal operation — otherwise no background jobs run and scheduled posts won't be sent:

docker-compose.yml — app (minimal setup)
  app:
    image: ${FEDISUITE_IMAGE:-christinloehner/fedisuite:latest}
    ...
    environment:
      - ENABLE_SCHEDULER=true      # ← changed
      - ENABLE_POSTS_REFRESH=true  # ← changed
      - ENABLE_IDLE_REMINDER=true  # ← changed
      - ENABLE_TIPS_ENGINE=true    # ← changed

You can then simply delete or comment out the worker1 and worker2 blocks from the docker-compose.yml.

When do I need multiple workers? Only when you expect many users on your instance. Workers process background jobs in parallel — for a single-person or small-group instance, a second worker makes little difference. You can switch from minimal to full operation at any time by adding the workers and setting the ENABLE_* flags in the app service back to false.

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.

Never delete this folder — it contains all the database data for your instance.
./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 read-only

Used by: app, worker1, worker2

Installed plugins. Mounted as read-only — the app can read plugins but not modify them.

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.