Lemmy: Federated Link Aggregator
Reddit alternative with ActivityPub federation. Self-hosted community with federation support.
Introduction
Lemmy is the open-source answer to Reddit’s centralized control. Built on the ActivityPub protocol (the same federation standard powering Mastodon), Lemmy allows you to host your own link aggregator that can communicate with other Lemmy instances and the broader Fediverse. This creates a decentralized network of communities where users aren’t locked into a single platform.
What you’ll learn:
- Deploy Lemmy with Docker Compose
- Configure PostgreSQL and image storage
- Set up email notifications with SMTP
- Enable federation with other instances
- Create and moderate communities
- Configure federation allowlists and blocklists
- Optimize performance and resource usage
- Implement backup and monitoring strategies
Prerequisites:
- Docker and Docker Compose installed
- Domain name with DNS configured
- Reverse proxy with SSL/TLS certificates
- 2GB+ RAM, 2+ CPU cores minimum
- 50GB+ storage for media and database
- SMTP email service
- Understanding of federation concepts helpful
Why Lemmy?
The Reddit Problem
Reddit’s evolution from community-driven platform to profit-focused corporation has created friction:
- API Changes: Killing third-party apps and tools
- Centralized Control: Single company controls all content
- Privacy Concerns: Extensive tracking and data collection
- Moderation Issues: Corporate overrides of community moderation
- No Data Ownership: Users can’t export or control their data
- Advertising: Increasing ads with poor user experience
How Lemmy Solves This
Lemmy provides a federated alternative with key advantages:
| Feature | Lemmy | |
|---|---|---|
| Ownership | Self-hosted, you own the data | Reddit Inc. owns everything |
| Federation | Connect to other instances | Centralized only |
| APIs | Full API access, no restrictions | Limited, expensive API |
| Ads | None (unless you add them) | Pervasive advertising |
| Privacy | You control privacy policy | Extensive tracking |
| Moderation | Community-controlled | Corporate oversight |
| Open Source | Fully open (AGPL) | Proprietary |
| Censorship | Instance-level decisions | Centralized censorship |
Key Features
For Users:
- Familiar Reddit-like interface
- Create posts, comment, vote (upvote/downvote)
- Subscribe to communities across instances
- Cross-instance interaction via federation
- User profiles with post history
- Private messaging
- Multiple themes and customization
For Instance Admins:
- Full control over instance policies
- Federation controls (allowlist, blocklist)
- User registration options (open, application-based, closed)
- CAPTCHA and anti-spam tools
- Resource usage controls
- Legal name and contact info display
For Community Moderators:
- Create and manage communities
- Assign moderator roles
- Remove posts and ban users
- Pin/feature posts
- Set community rules
- Restrict posting permissions
Federation (Fediverse Integration):
- Communicate with other Lemmy instances
- Federate with Mastodon, Pleroma, and other ActivityPub platforms
- Users from other instances can interact with your content
- Share communities across the Fediverse
Architecture Overview
Lemmy uses a modern multi-container architecture:
Internet → Reverse Proxy (HTTPS) → Lemmy UI (Frontend)
↓ ↓
Federation In/Out Lemmy Backend (Rust API)
↓ ↓
ActivityPub Protocol PostgreSQL Database
↓
Pictrs (Image Hosting)
↓
Object Storage / Disk Components:
- Lemmy Backend: Rust-based API server handling all business logic
- Lemmy UI: TypeScript/Inferno frontend serving the web interface
- PostgreSQL: Relational database for all content and metadata
- Pictrs: Image hosting service with resizing and format conversion
- Reverse Proxy: Nginx/Caddy providing SSL and routing
- SMTP Server: Email delivery for notifications and registration
Installation with Docker Compose
Step 1: Prepare Environment
# Create project directory
mkdir -p ~/lemmy && cd ~/lemmy
# Create subdirectories
mkdir -p volumes/postgres volumes/pictrs
# Create environment file
cat > .env <<EOF
# Instance info
LEMMY_HOSTNAME=lemmy.yourdomain.com
LEMMY_INSTANCE_NAME=My Lemmy Instance
# Database
POSTGRES_USER=lemmy
POSTGRES_PASSWORD=secure_database_password_here
POSTGRES_DB=lemmy
# Email (SMTP)
LEMMY_SMTP_SERVER=smtp.sendgrid.net:587
LEMMY_SMTP_LOGIN=apikey
LEMMY_SMTP_PASSWORD=YOUR_SENDGRID_API_KEY
[email protected]
# Admin
LEMMY_ADMIN_USERNAME=admin
LEMMY_ADMIN_PASSWORD=secure_admin_password
[email protected]
# Federation
LEMMY_FEDERATION_ENABLED=true
# Security
LEMMY_CAPTCHA_ENABLED=true
LEMMY_REGISTRATION_MODE=requireapplication
# JWT secret (generate with: openssl rand -base64 32)
LEMMY_JWT_SECRET=YOUR_GENERATED_JWT_SECRET_HERE
EOF
# Generate JWT secret
openssl rand -base64 32
# Secure environment file
chmod 600 .env Step 2: Create Lemmy Configuration
Create lemmy.hjson (Lemmy uses HJSON format):
{
# Instance settings
hostname: "lemmy.yourdomain.com"
# Database connection
database: {
host: postgres
port: 5432
database: lemmy
user: lemmy
password: "secure_database_password_here"
pool_size: 30
}
# Email settings
email: {
smtp_server: "smtp.sendgrid.net:587"
smtp_login: "apikey"
smtp_password: "YOUR_SENDGRID_API_KEY"
smtp_from_address: "[email protected]"
tls_type: "tls"
}
# Federation settings
federation: {
enabled: true
# Uncomment to use allowlist
# allowed_instances: [
# "lemmy.ml"
# "lemmy.world"
# ]
# Uncomment to block instances
# blocked_instances: [
# "spam-instance.com"
# ]
}
# Security
captcha: {
enabled: true
difficulty: "medium"
}
# Registration
registration_mode: "requireapplication" # or "open" or "closed"
# Rate limiting
rate_limit: {
message: 180
message_per_second: 60
post: 6
post_per_second: 600
register: 3
register_per_second: 3600
image: 6
image_per_second: 3600
}
# Image hosting (pictrs)
pictrs: {
url: "http://pictrs:8080/"
api_key: "API_KEY_HERE"
}
# Security settings
setup: {
admin_username: "admin"
admin_password: "secure_admin_password"
admin_email: "[email protected]"
site_name: "My Lemmy Instance"
}
# TLS is handled by reverse proxy
tls_enabled: false
} Step 3: Create Docker Compose Configuration
Create docker-compose.yml:
version: '3.8'
services:
# PostgreSQL Database
postgres:
image: postgres:16-alpine
container_name: lemmy-postgres
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- ./volumes/postgres:/var/lib/postgresql/data
networks:
- lemmy-internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
# Pictrs - Image hosting service
pictrs:
image: asonix/pictrs:0.5
container_name: lemmy-pictrs
restart: unless-stopped
environment:
- PICTRS__SERVER__API_KEY=CHANGE_THIS_API_KEY
- PICTRS__MEDIA__MAX_FILE_SIZE=40
- PICTRS__MEDIA__GIF__MAX_FILE_SIZE=40
- PICTRS__MEDIA__VIDEO__MAX_FILE_SIZE=100
- RUST_LOG=info
volumes:
- ./volumes/pictrs:/mnt
networks:
- lemmy-internal
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/healthz"]
interval: 10s
timeout: 5s
retries: 3
# Lemmy Backend (API)
lemmy:
image: dessalines/lemmy:latest
container_name: lemmy-backend
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
pictrs:
condition: service_healthy
environment:
- RUST_LOG=warn
volumes:
- ./lemmy.hjson:/config/config.hjson:ro
networks:
- lemmy-internal
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8536/api/v3/site"]
interval: 30s
timeout: 10s
retries: 3
# Lemmy UI (Frontend)
lemmy-ui:
image: dessalines/lemmy-ui:latest
container_name: lemmy-ui
restart: unless-stopped
depends_on:
- lemmy
environment:
- LEMMY_UI_LEMMY_INTERNAL_HOST=lemmy:8536
- LEMMY_UI_LEMMY_EXTERNAL_HOST=${LEMMY_HOSTNAME}
- LEMMY_UI_HTTPS=true
networks:
- lemmy-internal
ports:
- "8536:1234"
# Reverse proxy (optional, you may use external proxy)
nginx:
image: nginx:alpine
container_name: lemmy-nginx
restart: unless-stopped
depends_on:
- lemmy-ui
- lemmy
- pictrs
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
networks:
- lemmy-internal
networks:
lemmy-internal:
driver: bridge
volumes:
postgres:
pictrs: Step 4: Configure Nginx Reverse Proxy
Create nginx.conf:
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Rate limiting
limit_req_zone $binary_remote_addr zone=lemmy_ratelimit:10m rate=1r/s;
# Upstream definitions
upstream lemmy-ui {
server lemmy-ui:1234;
}
upstream lemmy-backend {
server lemmy:8536;
}
upstream pictrs {
server pictrs:8080;
}
# HTTP redirect to HTTPS
server {
listen 80;
server_name lemmy.yourdomain.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name lemmy.yourdomain.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
# File upload size
client_max_body_size 100M;
# Frontend
location / {
proxy_pass http://lemmy-ui;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Backend API
location ~ ^/(api|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Rate limiting
limit_req zone=lemmy_ratelimit burst=30 nodelay;
}
# Image hosting
location /pictrs {
proxy_pass http://pictrs;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
} Step 5: SSL Certificates
# Option 1: Let's Encrypt with Certbot
sudo certbot certonly --standalone -d lemmy.yourdomain.com
# Copy certificates
mkdir -p ssl
sudo cp /etc/letsencrypt/live/lemmy.yourdomain.com/fullchain.pem ssl/
sudo cp /etc/letsencrypt/live/lemmy.yourdomain.com/privkey.pem ssl/
sudo chown $USER:$USER ssl/*.pem
# Option 2: Self-signed (development only)
mkdir -p ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/privkey.pem -out ssl/fullchain.pem -subj "/CN=lemmy.yourdomain.com" Step 6: Launch Lemmy
# Start all services
docker-compose up -d
# Watch logs
docker-compose logs -f
# Verify all containers are running
docker-compose ps
# Check health
curl -k https://lemmy.yourdomain.com/api/v3/site Visit https://lemmy.yourdomain.com to see your Lemmy instance!
Email Configuration
SMTP Provider Examples
SendGrid:
email: {
smtp_server: "smtp.sendgrid.net:587"
smtp_login: "apikey"
smtp_password: "YOUR_SENDGRID_API_KEY"
smtp_from_address: "[email protected]"
tls_type: "tls"
} Mailgun:
email: {
smtp_server: "smtp.mailgun.org:587"
smtp_login: "[email protected]"
smtp_password: "YOUR_MAILGUN_PASSWORD"
smtp_from_address: "[email protected]"
tls_type: "tls"
} Gmail (App Password required):
email: {
smtp_server: "smtp.gmail.com:587"
smtp_login: "[email protected]"
smtp_password: "YOUR_APP_PASSWORD"
smtp_from_address: "[email protected]"
tls_type: "starttls"
} Testing Email
# Check logs for email errors
docker-compose logs lemmy | grep -i email
docker-compose logs lemmy | grep -i smtp
# Test by registering a new account
# Check inbox for verification email Federation Setup
Federation is Lemmy’s superpower - connecting your instance to the Fediverse.
Understanding Federation
How It Works:
- User on Instance A subscribes to community on Instance B
- Instance A sends follow request to Instance B
- Instance B accepts and begins sending activity updates
- Posts, comments, votes sync between instances
- Users can interact cross-instance seamlessly
Federation Modes:
Open Federation (Default): All instances can federate unless blocked
federation: {
enabled: true
} Allowlist Federation (Restricted): Only specific instances can federate
federation: {
enabled: true
allowed_instances: [
"lemmy.ml"
"lemmy.world"
"beehaw.org"
"lemm.ee"
]
} Blocklist Federation: Block problematic instances
federation: {
enabled: true
blocked_instances: [
"spam-instance.com"
"hateful-instance.org"
]
} Configuring Federation
Edit lemmy.hjson and restart:
# Edit configuration
nano lemmy.hjson
# Restart backend
docker-compose restart lemmy
# Verify federation
curl https://lemmy.yourdomain.com/.well-known/nodeinfo Testing Federation
1. Subscribe to Remote Community:
- Search for:
[email protected] - Example:
[email protected] - Click “Subscribe”
2. Verify in Logs:
docker-compose logs -f lemmy | grep -i federation 3. Check Federation Stats:
- Admin panel → Federation
- See connected instances
- Monitor activity flow
Federation Best Practices
Start Conservatively: Begin with allowlist of trusted instances, expand gradually
Monitor Activity: Watch for spam and abuse from federated instances
Legal Compliance: You’re responsible for federated content stored on your server
Resource Usage: More federation = more storage and bandwidth usage
Community Guidelines: Set clear rules for federated content
Community Creation and Management
Creating Communities
Click “Create Community”
Fill in details:
- Name: “technology” (lowercase, no spaces)
- Display name: “Technology Discussion”
- Sidebar: Markdown-formatted rules and info
- Icon: Upload square image (recommended 512x512)
- Banner: Upload banner (recommended 1920x384)
Configure Settings:
- Posting restricted to moderators: Off for open communities
- NSFW: Mark if adult content allowed
- Only moderators can post: For announcement-only communities
Community Moderation
Moderator Actions:
- Remove posts and comments
- Ban users from community
- Pin/feature posts
- Lock posts (disable comments)
- Edit community sidebar and rules
Ban User from Community:
- Click user’s name
- Click “Ban from community”
- Set duration and reason
- Optionally remove all their posts
Appoint Moderators:
- Community settings → Moderators
- Search for user
- Click “Add moderator”
- Set permissions
Community Rules
Add rules in community sidebar (Markdown):
# Community Rules
1. **Be Respectful** - No personal attacks or harassment
2. **Stay On Topic** - Posts must relate to [topic]
3. **No Spam** - Self-promotion limited to weekly thread
4. **Quality Content** - Put effort into posts
5. **No Illegal Content** - Follows site-wide rules
6. **Cite Sources** - Link to original content
7. **NSFW Tags** - Mark adult content appropriately Instance Administration
Admin Panel
Access: Click your username → Admin
Key Sections:
Site Settings:
- Instance name and sidebar
- Legal information (required for EU compliance)
- Default theme
- Registration mode
- CAPTCHA settings
Moderation:
- Review reported content
- Site-wide user bans
- Federated instance management
- View moderation log
Federation:
- View connected instances
- Allowlist/blocklist management
- Federation activity logs
User Registration Modes
Open Registration:
registration_mode: "open" Anyone can register without approval
Application Required:
registration_mode: "requireapplication" Users must apply with reason, admins approve
Closed Registration:
registration_mode: "closed" No new registrations allowed
Approving Applications
- Admin panel → Registration Applications
- Review application
- Approve or deny with reason
- User receives email notification
Site-Wide Moderation
Ban User Across Instance:
- User profile → Ban from site
- Set duration and reason
- Optionally purge all content
Remove Content:
- View post/comment
- Click “Remove”
- Provide reason (visible to moderators)
Handle Reports:
- Admin → Moderation
- Review reported content
- Take action or dismiss
Performance Optimization
Resource Requirements
Small Instance (100 users, 10 communities):
- 2GB RAM
- 2 CPU cores
- 50GB storage
Medium Instance (1000 users, 100 communities):
- 4GB RAM
- 4 CPU cores
- 200GB storage
Large Instance (10k+ users, federation heavy):
- 8GB+ RAM
- 8+ CPU cores
- 500GB+ storage
Database Optimization
# Enter PostgreSQL container
docker exec -it lemmy-postgres psql -U lemmy
-- Vacuum and analyze
VACUUM ANALYZE;
-- Check database size
SELECT pg_size_pretty(pg_database_size('lemmy'));
-- Find largest tables
SELECT schemaname AS schema,
tablename AS table,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 10;
-- Exit
q Image Storage Optimization
Pictrs automatically converts and optimizes images. Configure limits:
pictrs:
environment:
- PICTRS__MEDIA__MAX_FILE_SIZE=40 # MB
- PICTRS__MEDIA__VIDEO__MAX_FILE_SIZE=100 # MB
- PICTRS__MEDIA__QUALITY=85 # JPEG quality Caching
Add Redis for caching (optional):
services:
redis:
image: redis:7-alpine
container_name: lemmy-redis
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 256mb
volumes:
- ./volumes/redis:/data
networks:
- lemmy-internal Update lemmy.hjson:
cache: {
type: "redis"
url: "redis://redis:6379"
} Backup and Recovery
Automated Backup Script
cat > ~/lemmy/backup.sh <<'EOF'
#!/bin/bash
BACKUP_DIR="/backups/lemmy"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p ${BACKUP_DIR}
# Backup database
docker exec lemmy-postgres pg_dump -U lemmy lemmy | gzip > ${BACKUP_DIR}/db-${DATE}.sql.gz
# Backup volumes
tar czf ${BACKUP_DIR}/postgres-${DATE}.tar.gz -C ~/lemmy/volumes postgres
tar czf ${BACKUP_DIR}/pictrs-${DATE}.tar.gz -C ~/lemmy/volumes pictrs
# Backup configuration
cp ~/lemmy/lemmy.hjson ${BACKUP_DIR}/config-${DATE}.hjson
cp ~/lemmy/docker-compose.yml ${BACKUP_DIR}/compose-${DATE}.yml
# Remove old backups (30 days)
find ${BACKUP_DIR} -name "*.gz" -mtime +30 -delete
find ${BACKUP_DIR} -name "*.hjson" -mtime +30 -delete
echo "Backup completed: ${DATE}"
EOF
chmod +x ~/lemmy/backup.sh
# Schedule daily backups
crontab -e
# Add: 0 2 * * * /root/lemmy/backup.sh Restore from Backup
# Stop containers
docker-compose down
# Restore database
gunzip -c /backups/lemmy/db-TIMESTAMP.sql.gz | docker exec -i lemmy-postgres psql -U lemmy lemmy
# Restore volumes
tar xzf /backups/lemmy/postgres-TIMESTAMP.tar.gz -C ~/lemmy/volumes/
tar xzf /backups/lemmy/pictrs-TIMESTAMP.tar.gz -C ~/lemmy/volumes/
# Start containers
docker-compose up -d Troubleshooting
Common Issues
Issue: Federation not working
# Check federation logs
docker-compose logs lemmy | grep -i federation
# Verify nodeinfo accessible
curl https://lemmy.yourdomain.com/.well-known/nodeinfo
# Check firewall allows outbound HTTPS
curl -I https://lemmy.ml
# Verify DNS is correct
dig lemmy.yourdomain.com Issue: Images not uploading
# Check pictrs logs
docker-compose logs pictrs
# Verify pictrs API key matches
grep api_key lemmy.hjson
# Check disk space
df -h
# Test pictrs directly
curl http://localhost:8080/healthz Issue: Email not sending
# Check SMTP configuration
docker-compose logs lemmy | grep -i smtp
# Test SMTP connectivity
docker exec lemmy-backend curl -v telnet://smtp.sendgrid.net:587 Issue: High resource usage
# Check container stats
docker stats
# Identify heavy processes
docker exec lemmy-postgres top
# Review federation activity
# Large instances federating = more resources Issue: Can’t log in as admin
# Reset admin password via database
docker exec -it lemmy-postgres psql -U lemmy
UPDATE person
SET password_encrypted = crypt('newpassword', gen_salt('bf'))
WHERE name = 'admin';
q Updating Lemmy
Update Process
# Backup first!
./backup.sh
# Pull latest images
docker-compose pull
# Restart with new images
docker-compose up -d
# Check logs
docker-compose logs -f
# Verify version
curl https://lemmy.yourdomain.com/api/v3/site | jq '.version' Version Pinning
Pin specific versions in docker-compose.yml:
lemmy:
image: dessalines/lemmy:0.19.3 # Specific version
lemmy-ui:
image: dessalines/lemmy-ui:0.19.3 Conclusion
You now have a fully functional federated Lemmy instance with:
- Secure HTTPS with SSL certificates
- Federation enabled for Fediverse connectivity
- Email notifications configured
- Image hosting with Pictrs
- Moderation tools ready
- Automated backups
- Performance optimizations
Next Steps
- Create your first communities around your interests
- Federate with popular instances (lemmy.ml, lemmy.world)
- Customize instance appearance with logo and theme
- Set registration policy based on your goals
- Establish community guidelines and rules
- Monitor resource usage and scale as needed
- Engage with the Fediverse - discover communities
Resources
Welcome to the federated future of link aggregation and discussion!