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

DateChange
2025-10-30Fixed mixed-content issues by enabling HTTPS settings and proper proxy headers in Lemmy behind Traefik.
2025-09-26Initial 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.
  • 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. 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.

  1. Domain Name: In config/nginx.conf, config/lemmy.hjson, and docker-compose.yml, replace all instances of lemmy.your-domain.com with your actual domain.
  2. Secrets: In config/lemmy.hjson and docker-compose.yml, paste the secrets you generated in step 3.1. Ensure the Postgres password and Pictrs API key are identical where required.
  3. Email Settings: In config/lemmy.hjson, update the email section 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

  1. Check Running Containers:

    docker ps --format '{{.Names}}'
    

    You should see lemmy-proxy, lemmy, lemmy-ui, pictrs, and postgres running.

  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 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 nginx.conf.

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.

๐Ÿ“šOFFICIAL LEMMY DOCS ๐Ÿ“ฆOFFICIAL DOCKER REPO