Deploying Lemmy with Docker and Traefik
Table of Contents ๐
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
Date | Change |
---|---|
2025-09-25 | Initial 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.
Domain Name:
- In
docker-compose.yml
, replacelemmy.your-domain.com
in thelemmy-ui
serviceโs environment variables and labels. - In
lemmy.hjson
, replacelemmy.your-domain.com
in thehostname
setting.
- In
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.
- In
Email Settings (in
lemmy.hjson
):- Update the
email
section with your own SMTP server details. This is required for user registration and notifications.
- Update the
โ ๏ธ CHECK VALUES CAREFULLY |
Ensure the Postgres password and Pictrs API key are identical in both |
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:
Purpose | Typical Nginx Location | Traefik 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.io | Router lemmy-backend โ Service port 8536 , higher priority 100 |
Pictrs (images) | /pictrs | Router pictrs + middleware pictrs-strip (StripPrefix /pictrs ) โ Service port 8080 |
TLS termination | listen 443 ssl; + certs | Traefik ACME tls_resolver on entrypoint websecure |
Security headers | add_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.
๐