This guide will walk you through deploying Lemmy, a popular, federated alternative to sites like Reddit. We will use Docker Compose to orchestrate the entire stack, which includes the Lemmy backend, the user interface, the Pictrs image store, and a Postgres database.

This setup is designed to integrate seamlessly with an existing Traefik v3 reverse proxy, ensuring your instance is secure, efficient, and protected by CrowdSec from day one.

Changelog

DateChange
2025-09-25Initial Version: Guide created, focusing on a Traefik-native setup without a separate Nginx proxy, and integration with the modern security stack.

1. Prerequisites

This guide builds upon a secure Docker environment. Before you begin, you must have a fully functional Traefik v3 and CrowdSec stack.

โš ๏ธ HARD REQUIREMENT

The following steps will not work correctly without the Traefik stack running as described in the prerequisite guide.

You will also need:

  • A dedicated subdomain for your Lemmy instance (e.g., lemmy.your-domain.com) pointed to your serverโ€™s IP address.
  • sudo or root access.
  • The openssl utility for generating secrets (sudo apt install openssl).

2. Directory Structure

First, create a dedicated directory for your Lemmy configuration and data.

# Create the main directory
sudo mkdir -p /opt/containers/lemmy
cd /opt/containers/lemmy

# Create directories for persistent data
sudo mkdir -p volumes/pictrs
sudo mkdir -p volumes/postgres

3. Configuration

We will create two main configuration files: docker-compose.yml to define the services and lemmy.hjson to configure the Lemmy application itself.

3.1. Generate Secrets

Before creating the configuration files, letโ€™s generate the necessary passwords and API keys.

# Generate a strong password for the Postgres database
POSTGRES_PASSWORD=$(openssl rand -hex 32)
echo "Your Postgres password is: $POSTGRES_PASSWORD"

# Generate a strong API key for Pictrs
PICTRS_API_KEY=$(openssl rand -hex 32)
echo "Your Pictrs API key is: $PICTRS_API_KEY"

# Generate a strong password for the Lemmy admin user
LEMMY_ADMIN_PASSWORD=$(openssl rand -hex 32)
echo "Your Lemmy Admin password is: $LEMMY_ADMIN_PASSWORD"
โ„น๏ธ SAVE THESE SECRETS

Copy these generated values into a temporary text file. You will need them in the following steps.

3.2. Docker Compose (docker-compose.yml)

This file orchestrates all the services needed to run Lemmy. It has been adapted to work directly with Traefik, removing the need for an additional Nginx proxy.

sudo tee docker-compose.yml > /dev/null << 'EOF'
services:
  lemmy:
    image: dessalines/lemmy:0.19.3
    hostname: lemmy
    container_name: lemmy
    restart: unless-stopped
    environment:
      - RUST_LOG=warn,lemmy_server=info
    volumes:
      - ./lemmy.hjson:/config/config.hjson
    depends_on:
      - postgres
      - pictrs
    networks:
      - proxy
      - lemmy-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.lemmy-backend.rule=Host(`lemmy.your-domain.com`) && (PathPrefix(`/api`) || PathPrefix(`/feeds`) || PathPrefix(`/.well-known`) || PathPrefix(`/inbox`) || PathPrefix(`/outbox`) || PathPrefix(`/federation`) || PathPrefix(`/nodeinfo`) || PathPrefix(`/socket.io`))"
      - "traefik.http.routers.lemmy-backend.entrypoints=websecure"
      - "traefik.http.routers.lemmy-backend.tls.certresolver=tls_resolver"
      - "traefik.http.routers.lemmy-backend.middlewares=security-headers@file,crowdsec-bouncer@docker"
      - "traefik.http.routers.lemmy-backend.priority=100"
      - "traefik.http.services.lemmy-backend.loadbalancer.server.port=8536"
      - "traefik.docker.network=proxy"

  lemmy-ui:
    image: dessalines/lemmy-ui:0.19.3
    container_name: lemmy-ui
    hostname: lemmy-ui
    restart: unless-stopped
    environment:
      - LEMMY_UI_LEMMY_INTERNAL_HOST=lemmy:8536
      - LEMMY_UI_LEMMY_EXTERNAL_HOST=lemmy.your-domain.com
      - LEMMY_UI_HTTPS=true # External HTTPS is terminated by Traefik
    depends_on:
      - lemmy
    networks:
      - proxy
      - lemmy-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.lemmy.rule=Host(`lemmy.your-domain.com`)"
      - "traefik.http.routers.lemmy.entrypoints=websecure"
      - "traefik.http.routers.lemmy.tls.certresolver=tls_resolver"
      - "traefik.http.routers.lemmy.priority=10"
      - "traefik.http.routers.lemmy.middlewares=security-headers@file,crowdsec-bouncer@docker"
      - "traefik.http.services.lemmy.loadbalancer.server.port=1234"
      - "traefik.docker.network=proxy"

  pictrs:
    image: asonix/pictrs:0.5.1
    hostname: pictrs
    container_name: pictrs
    restart: unless-stopped
    environment:
      - PICTRS__API_KEY=PASTE-YOUR-PICTRS-API-KEY-HERE
      - RUST_LOG=info
    user: 991:991
    volumes:
      - ./volumes/pictrs:/mnt
    networks:
      - proxy
      - lemmy-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pictrs.rule=Host(`lemmy.your-domain.com`) && PathPrefix(`/pictrs`)"
      - "traefik.http.routers.pictrs.entrypoints=websecure"
      - "traefik.http.routers.pictrs.tls.certresolver=tls_resolver"
      - "traefik.http.routers.pictrs.middlewares=security-headers@file,crowdsec-bouncer@docker,pictrs-strip@docker"
      - "traefik.http.middlewares.pictrs-strip.stripprefix.prefixes=/pictrs"
      - "traefik.http.services.pictrs.loadbalancer.server.port=8080"
      - "traefik.docker.network=proxy"

  postgres:
    image: postgres:16-alpine
    hostname: postgres
    container_name: postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=lemmy
      - POSTGRES_PASSWORD=PASTE-YOUR-POSTGRES-PASSWORD-HERE
      - POSTGRES_DB=lemmy
    volumes:
      - ./volumes/postgres:/var/lib/postgresql/data
    networks:
      - lemmy-net

networks:
  proxy:
    external: true
  lemmy-net:
    driver: bridge
EOF

3.3. Lemmy Configuration (lemmy.hjson)

This file controls the behavior of the Lemmy application itself. Create it and paste the contents below.

sudo tee lemmy.hjson > /dev/null << 'EOF'
{
  database: {
    user: "lemmy"
    password: "PASTE-YOUR-POSTGRES-PASSWORD-HERE"
    host: "postgres"
    port: 5432
    database: "lemmy"
  }
  pictrs: {
    url: "http://pictrs:8080/"
    api_key: "PASTE-YOUR-PICTRS-API-KEY-HERE"
  }
  email: {
    smtp_server: "your-mail-server.com:587"
    smtp_login: "lemmy@your-domain.com"
    smtp_password: "PASTE-YOUR-EMAIL-PASSWORD-HERE"
    smtp_from_address: "Lemmy <lemmy@your-domain.com>"
    tls_type: "starttls"
  }
  setup: {
    admin_username: "admin"
    admin_password: "PASTE-YOUR-LEMMY-ADMIN-PASSWORD-HERE"
    site_name: "My Lemmy Instance"
    admin_email: "your-admin@example.com"
  }
  hostname: "lemmy.your-domain.com"
  bind: "0.0.0.0"
  port: 8536
  tls_enabled: false
}
EOF

3.4. Update Configuration with Your Values

Now, replace all the placeholder values in both files.

  1. Domain Name:

    • In docker-compose.yml, replace lemmy.your-domain.com in the lemmy-ui serviceโ€™s environment variables and labels.
    • In lemmy.hjson, replace lemmy.your-domain.com in the hostname setting.
  2. Passwords and API Keys:

    • In docker-compose.yml, paste your generated Pictrs API key.
    • In docker-compose.yml, paste your generated Postgres password.
    • In lemmy.hjson, paste your Postgres password, Pictrs API key, and the Lemmy admin password.
  3. Email Settings (in lemmy.hjson):

    • Update the email section with your own SMTP server details. This is required for user registration and notifications.
โš ๏ธ CHECK VALUES CAREFULLY

Ensure the Postgres password and Pictrs API key are identical in both docker-compose.yml and lemmy.hjson.

3.5. Why Nginx Is Not Needed (Traefik Handles the Reverse Proxy)

The official Lemmy documentation often shows an Nginx reverse proxy in front of the services to handle TLS, routing (API, federation endpoints, image store), and headers. In this guide, Traefik replaces Nginx and provides the same functionality via Docker labels and reusable middlewares.

Mapping of typical Nginx locations to Traefik components used here:

PurposeTypical Nginx LocationTraefik Equivalent
UI (frontend)/Router lemmy โ†’ Service port 1234 (lemmy-ui), lower priority 10 so backend paths win
Backend API & Federation/api, /.well-known, /feeds, /inbox, /outbox, /federation, /nodeinfo, /socket.ioRouter lemmy-backend โ†’ Service port 8536, higher priority 100
Pictrs (images)/pictrsRouter pictrs + middleware pictrs-strip (StripPrefix /pictrs) โ†’ Service port 8080
TLS terminationlisten 443 ssl; + certsTraefik ACME tls_resolver on entrypoint websecure
Security headersadd_header ...Middleware security-headers@file (file provider)
Abuse protectionโ€”CrowdSec bouncer plugin crowdsec-bouncer@docker applied per router

Notes:

  • WebSockets (e.g., /socket.io) are proxied transparently by Traefik; no extra config is required beyond the router rule.
  • Lemmyโ€™s internal TLS is disabled (tls_enabled: false) because TLS is terminated by Traefik.
  • By defining middlewares per router instead of globally, you can decide exactly which services are protected by CrowdSec.

4. Launch the Stack

With all configuration files in place, you can start your Lemmy instance.

# From within the /opt/containers/lemmy directory
sudo docker compose up -d

The first launch will take a few minutes as Docker downloads the images and the database is initialized. You can monitor the progress with:

sudo docker compose logs -f
```Press `CTRL+C` to exit the logs view.

## 5. Verify the Installation

After a few minutes, your Lemmy instance should be running and accessible.

1.  **Check Running Containers:**
    ```bash
    docker ps --format '{{.Names}}' | grep -E 'lemmy|pictrs|postgres'
    ```
    You should see `lemmy`, `lemmy-ui`, `pictrs`, and `postgres` in the list.

2.  **Access Your Site:**
    Open a web browser and navigate to `https://lemmy.your-domain.com`. You should see the Lemmy welcome page.

3.  **Log In as Admin:**
    Log in with the admin username (`admin` by default) and the strong password you generated and placed in `lemmy.hjson`. You can now start configuring your instance, creating communities, and adjusting federation settings from the site settings panel.

## 6. Maintenance

### Updating Lemmy

To update your Lemmy instance to a newer version, you only need to change the image tags in your `docker-compose.yml` and restart the stack.

```bash
cd /opt/containers/lemmy

# Pull the latest images specified in your compose file
sudo docker compose pull

# Restart the stack with the new images
sudo docker compose up -d --remove-orphans
๐Ÿ’ก CHECK RELEASE NOTES

Always check the official Lemmy release notes before updating, as there may be breaking changes or specific instructions for major version upgrades.

Backing Up

A complete backup consists of the database and the uploaded images (pictrs).

# In your /opt/containers/lemmy directory
# 1. Stop Lemmy to ensure data consistency
sudo docker compose stop lemmy lemmy-ui

# 2. Back up the database
sudo docker compose exec -T postgres pg_dump -U lemmy -d lemmy | gzip > lemmy_db_backup_$(date +%F).sql.gz

# 3. Back up the images
sudo tar -czvf pictrs_backup_$(date +%F).tar.gz volumes/pictrs

# 4. Restart the services
sudo docker compose start lemmy lemmy-ui

Conclusion

You have successfully deployed a secure and modern Lemmy instance. By leveraging Traefik for reverse proxying and CrowdSec for security, your community platform is built on a robust and scalable foundation. You are now ready to join the Fediverse and build your own online community.

๐Ÿ“šOFFICIAL LEMMY DOCS