Skip to main content

Installing Lemmy on YunoHost (Docker Method)

This guide documents the process of installing Lemmy (a federated link aggregator) on a VPS running YunoHost, utilizing Docker Compose. This "hybrid" approach allows Docker to manage the application dependencies while letting YunoHost handle the domain, DNS, and SSL certificates via a standard Reverse Proxy.

Prerequisites

  • OS: Debian (via YunoHost 11.x or newer).
  • Software: Docker & Docker Compose installed.
  • Domain: A dedicated subdomain (e.g., lemmy.nulu.my) added to the YunoHost admin panel.
  • Access: SSH root/sudo access.

1. Domain Preparation

Before touching Docker, ensure the domain is ready in YunoHost.

  1. Log in to the YunoHost Web Admin.
  2. Navigate to Domains > Add Domain.
  3. Add your subdomain (e.g., lemmy.nulu.my).
  4. Navigate to Tools > Diagnosis to ensure DNS records are propagated (specifically the A record pointing to your VPS IP).

2. File Structure Setup

Create a dedicated directory to keep the installation clean.

# Create project directories
mkdir -p ~/docker/lemmy/config
mkdir -p ~/docker/lemmy/nginx
mkdir -p ~/docker/lemmy/volumes/postgres
mkdir -p ~/docker/lemmy/volumes/pictrs

cd ~/docker/lemmy


3. Configuration Files

We need three main files: lemmy.hjson (app config), nginx.conf (internal proxy), and docker-compose.yml.

A. Lemmy Configuration (config/lemmy.hjson)

Create the file: nano config/lemmy.hjson

{
  database: {
    host: "postgres"
    password: "YOUR_DB_PASSWORD"
    user: "lemmy"
    database: "lemmy"
  }
  hostname: "lemmy.nulu.my"
  pictrs: {
    url: "http://pictrs:8080"
    api_key: "YOUR_PICTRS_API_KEY"
  }
  setup: {
    admin_username: "your_username"
    admin_password: "YOUR_ADMIN_PASSWORD"
    site_name: "My Lemmy Instance"
    admin_email: "email@example.com"
  }
}

Critical Note: Do not include allow_signups: true in the setup block. This setting is deprecated in config files and will cause the container to crash with a "Main Panic" error. This setting is now managed via the Web UI.

B. Internal Nginx Proxy (nginx/nginx.conf)

This internal proxy routes traffic between the frontend (UI) and the backend (API), presenting a single port (8536) to YunoHost.

Create the file: nano nginx/nginx.conf

events { worker_connections 1024; }

http {
    upstream lemmy { server lemmy:8536; }
    upstream lemmy-ui { server lemmy-ui:1234; }

    server {
        listen 80;
        server_name lemmy.nulu.my;

        # Frontend (The UI)
        location / {
            proxy_pass http://lemmy-ui;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # Backend (The API and Images)
        location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) {
            proxy_pass http://lemmy;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            
            # Websocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
}

C. Docker Compose (docker-compose.yml)

Create the file: nano docker-compose.yml

services:
  proxy:
    image: nginx:1-alpine
    hostname: proxy
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    # Binds to local port 8536. YunoHost will proxy to this.
    ports:
      - "127.0.0.1:8536:80"
    depends_on:
      - lemmy
      - lemmy-ui
    restart: always

  lemmy:
    image: dessalines/lemmy:0.19.3
    hostname: lemmy
    restart: always
    volumes:
      - ./config/lemmy.hjson:/config/config.hjson
    depends_on:
      - postgres
      - pictrs
    logging:
      driver: "json-file"
      options:
        max-size: "50m"

  lemmy-ui:
    image: dessalines/lemmy-ui:0.19.3
    hostname: lemmy-ui
    restart: always
    environment:
      - LEMMY_UI_LEMMY_INTERNAL_HOST=lemmy:8536
      - LEMMY_UI_LEMMY_EXTERNAL_HOST=lemmy.nulu.my
      - LEMMY_UI_HTTPS=true
    depends_on:
      - lemmy

  postgres:
    image: postgres:15-alpine
    hostname: postgres
    environment:
      - POSTGRES_USER=lemmy
      - POSTGRES_PASSWORD=YOUR_DB_PASSWORD
      - POSTGRES_DB=lemmy
    volumes:
      - ./volumes/postgres:/var/lib/postgresql/data
    restart: always

  pictrs:
    image: asonix/pictrs:0.5
    hostname: pictrs
    volumes:
      - ./volumes/pictrs:/mnt
    environment:
      - PICTRS__SERVER__API_KEY=YOUR_PICTRS_API_KEY
    restart: always
    logging:
      driver: "json-file"
      options:
        max-size: "50m"


4. Permission Fixes

Docker containers often run as specific non-root users (Postgres uses UID 70 or 999; Pictrs uses UID 991). If folders are created by root, the containers will fail to start.

Run these commands to fix ownership:

sudo chown -R 991:991 volumes/pictrs
sudo chown -R 70:70 volumes/postgres


5. Deployment

Launch the stack:

docker compose up -d

Check logs to ensure stability:

docker compose logs -f


6. YunoHost Integration (The "Redirect" App)

Now that Lemmy is running locally on port 8536, expose it via YunoHost.

  1. Go to YunoHost Admin > Applications > Install.
  2. Select Redirect.
  3. Configuration:
  • Domain: lemmy.nulu.my
  • Path: /
  • Type: Proxy (Important!)
  • Destination: http://127.0.0.1:8536
  1. Click Install.

YunoHost will now generate the SSL certificate via Let's Encrypt and route incoming traffic to your Docker container.


Troubleshooting / Lessons Learnt

  1. 502 Bad Gateway: Usually means the container is crashing. Check logs with docker compose logs.
  2. Panic "Unknown field allow_signups": The lemmy.hjson format changed in recent versions. Remove allow_signups from the config file and manage it in the UI.
  3. Raw JSON instead of UI: If you see raw code when visiting the site, you are hitting the API directly. Ensure you have the lemmy-ui service running and the internal Nginx proxy correctly routing / requests to lemmy-ui and /api requests to lemmy.