Deploying Lemmy with Docker, Traefik, and Nginx
Table of Contents ๐
- Changelog
- 1. Prerequisites
- 2. Directory Structure
- 3. Configuration
- 4. Launch the Stack
- 5. Verify the Installation
- 6. Maintenance
- Conclusion
This guide provides a comprehensive walkthrough for deploying a secure and scalable Lemmy instance. We will use the official deployment pattern, which includes an Nginx container as an internal reverse proxy to manage application-specific routing. This entire stack is placed behind our modern Traefik v3 reverse proxy, which handles TLS, security, and public-facing routing.
This โproxy-behind-a-proxyโ architecture is highly recommended as it simplifies Traefikโs configuration and makes your Lemmy instance more robust and easier to update, closely following the official deployment standards.
Changelog
| Date | Change |
|---|---|
| 2025-10-30 | Fixed mixed-content issues by enabling HTTPS settings and proper proxy headers in Lemmy behind Traefik. |
| 2025-09-26 | Initial Version: Guide created using the official Nginx internal proxy method for maximum compatibility and update safety, integrated with the Traefik v3 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. sudoor root access.- The
opensslutility for generating secrets (sudo apt install openssl).
2. Directory Structure
First, create a dedicated directory for your Lemmy configuration and data. This structure will hold all necessary config files.
# Create the main directory
sudo mkdir -p /opt/containers/lemmy
cd /opt/containers/lemmy
# Create directories for persistent data and configs
sudo mkdir -p volumes/pictrs
sudo mkdir -p volumes/postgres
sudo mkdir -p config
sudo chown -R 991:991 volumes/pictrs
3. Configuration
We will now create all the necessary configuration files for Nginx, PostgreSQL, Lemmy, and Docker Compose.
3.1. Generate Secrets
Letโs generate strong, unique secrets for the database, image service, and admin account.
# 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 to paste them into the configuration files in the upcoming steps. |
3.2. Nginx Configuration (config/nginx.conf)
This file contains the internal routing logic that directs traffic to either the Lemmy UI, the backend, or the image store based on the request path and headers.
sudo tee config/nginx.conf > /dev/null << 'EOF'
worker_processes 1;
events {
worker_connections 1024;
}
http {
upstream lemmy { server "lemmy:8536"; }
upstream lemmy-ui { server "lemmy-ui:1234"; }
server {
listen 1236;
listen 8536;
server_name lemmy.criticalbasics.xyz;
server_tokens off;
gzip on;
gzip_types text/css application/javascript image/svg+xml;
gzip_vary on;
client_max_body_size 20M;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
location / {
set $proxpass "http://lemmy-ui";
if ($http_accept = "application/activity+json") { set $proxpass "http://lemmy"; }
if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") { set $proxpass "http://lemmy"; }
if ($request_method = POST) { set $proxpass "http://lemmy"; }
proxy_pass $proxpass;
rewrite ^(.+)/+$ $1 permanent;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl on;
}
location ~ ^/(api|pictrs|feeds|inbox|outbox|nodeinfo|version|socket\.io|federation|\.well-known) {
proxy_pass "http://lemmy";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl on;
proxy_redirect off;
}
}
}
EOF
3.3. PostgreSQL Configuration
For better performance, weโll provide a custom configuration file for PostgreSQL.
config/custom.conf (Performance Tuning)
sudo tee config/custom.conf > /dev/null << 'EOF'
# Generated by https://pgtune.leopard.in.ua
# DB Version: 16, OS: linux, RAM: 12 GB, CPUs: 16, Storage: ssd
max_connections = 200
shared_buffers = 3GB
effective_cache_size = 9GB
maintenance_work_mem = 768MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 3932kB
min_wal_size = 1GB
max_wal_size = 8GB
max_worker_processes = 16
max_parallel_workers_per_gather = 4
max_parallel_workers = 16
max_parallel_maintenance_workers = 4
EOF
config/pg_hba.conf (Authentication Rules)
sudo tee config/pg_hba.conf > /dev/null << 'EOF'
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all 0.0.0.0/0 md5
host all all ::/0 md5
EOF
3.4. Lemmy Configuration (config/lemmy.hjson)
This file controls the Lemmy application itself.
sudo tee config/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: true
}
EOF
3.5. Docker Compose (docker-compose.yml)
This file ties all the services together. Notice that only the lemmy-proxy service has Traefik labels and is exposed to the external proxy network.
sudo tee docker-compose.yml > /dev/null << 'EOF'
services:
lemmy-proxy:
image: nginx:stable-alpine
container_name: lemmy-proxy
restart: unless-stopped
volumes:
- ./config/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- lemmy
- lemmy-ui
- pictrs
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.middlewares=security-headers@file,crowdsec-bouncer@docker"
- "traefik.http.services.lemmy.loadbalancer.server.port=8536"
- "traefik.docker.network=proxy"
lemmy:
image: dessalines/lemmy:0.19.3
hostname: lemmy
container_name: lemmy
restart: unless-stopped
environment:
- RUST_LOG=warn,lemmy_server=info
volumes:
- ./config/lemmy.hjson:/config/config.hjson:ro
depends_on:
- postgres
- pictrs
networks:
- lemmy-net
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
depends_on:
- lemmy
networks:
- lemmy-net
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:
- lemmy-net
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
- ./config/custom.conf:/etc/postgresql/postgresql.conf:ro
- ./config/pg_hba.conf:/var/lib/postgresql/data/pg_hba.conf:ro
networks:
- lemmy-net
networks:
proxy:
external: true
lemmy-net:
driver: bridge
EOF
3.6. Update Configuration with Your Values
Carefully replace all placeholders in the files you just created.
- Domain Name: In
config/nginx.conf,config/lemmy.hjson, anddocker-compose.yml, replace all instances oflemmy.your-domain.comwith your actual domain. - Secrets: In
config/lemmy.hjsonanddocker-compose.yml, paste the secrets you generated in step 3.1. Ensure the Postgres password and Pictrs API key are identical where required. - Email Settings: In
config/lemmy.hjson, update theemailsection with your SMTP server details.
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 all the necessary images. You can monitor the progress with:
sudo docker compose logs -f
Press CTRL+C to exit the logs view once the services are stable.
5. Verify the Installation
Check Running Containers:
docker ps --format '{{.Names}}'You should see
lemmy-proxy,lemmy,lemmy-ui,pictrs, andpostgresrunning.Access Your Site: Open a web browser and navigate to
https://lemmy.your-domain.com. You should see the Lemmy welcome page.Log In as Admin: Log in using the admin username (
admin) and the strong password you generated for it. You can now begin administering your instance.
6. Maintenance
Updating Lemmy
Updating is now safer. You typically only need to update the image tags in docker-compose.yml for lemmy, lemmy-ui, and pictrs.
cd /opt/containers/lemmy
# Pull the new images defined 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 consult the official Lemmy release notes before updating for any special instructions or potential changes to configuration files like |
Backing Up
A complete backup consists of the database and the uploaded images.
# From your /opt/containers/lemmy directory
# 1. Stop services to ensure data consistency
sudo docker compose stop lemmy lemmy-ui lemmy-proxy
# 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 services
sudo docker compose start
Conclusion
You have now deployed a production-ready Lemmy instance using a robust and maintainable architecture. By combining the power of Traefik for edge routing and security with the official Nginx proxy for internal application logic, your instance is scalable, secure, and easy to manage for years to come.


