15 KiB
Pleroma Docker Setup Guide
This guide outlines the setup of a Pleroma instance (backend and custom frontend) using Docker Compose, including Nginx reverse proxy, Cloudflared tunneling, persistent volumes, custom networking, updates, monitoring, and resource limits. The current date is March 19, 2025.
Initial Docker Compose Setup
Pleroma doesn’t have an official Docker image, so we build it from source. Here’s the initial docker-compose.yaml
with backend, frontend, and PostgreSQL:
docker-compose.yaml
version: "3.8"
services:
db:
image: postgres:15-alpine
container_name: pleroma_db
restart: always
environment:
POSTGRES_USER: pleroma
POSTGRES_PASSWORD: your_secure_password_here
POSTGRES_DB: pleroma
volumes:
- ./postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "pleroma"]
interval: 10s
timeout: 5s
retries: 5
pleroma_backend:
build:
context: ./pleroma_backend
dockerfile: Dockerfile
container_name: pleroma_backend
restart: always
ports:
- "4000:4000"
environment:
DOMAIN: yourdomain.com
INSTANCE_NAME: YourPleromaInstance
ADMIN_EMAIL: admin@yourdomain.com
NOTIFY_EMAIL: notify@yourdomain.com
DB_USER: pleroma
DB_PASS: your_secure_password_here
DB_NAME: pleroma
volumes:
- ./uploads:/var/lib/pleroma/uploads
- ./static:/var/lib/pleroma/static
- ./config:/etc/pleroma
depends_on:
db:
condition: service_healthy
command: ["/bin/sh", "-c", "mix ecto.migrate && ./bin/pleroma start"]
pleroma_frontend:
build:
context: ./pleroma_frontend
dockerfile: Dockerfile
container_name: pleroma_frontend
restart: always
volumes:
- ./pleroma_frontend/dist:/app/dist
Backend (pleroma_backend/Dockerfile)
FROM elixir:1.14-alpine AS builder
RUN apk add --no-cache git build-base cmake postgresql-dev
WORKDIR /pleroma
RUN git clone https://git.pleroma.social/pleroma/pleroma.git .
RUN mix local.hex --force && mix local.rebar --force
RUN mix deps.get
RUN mix compile
RUN mkdir -p /etc/pleroma
FROM elixir:1.14-alpine
RUN apk add --no-cache postgresql-client
WORKDIR /pleroma
COPY --from=builder /pleroma /pleroma
ENV MIX_ENV=prod
EXPOSE 4000
CMD ["/bin/sh"]
Frontend (pleroma_frontend/Dockerfile)
FROM node:18-alpine AS builder
WORKDIR /app
RUN apk add --no-cache git && \
git clone https://git.pleroma.social/pleroma/pleroma-fe.git .
RUN npm install
RUN npm run build
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/dist /app/dist
Configuration file (config/config.exs)
use Mix.Config
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "yourdomain.com", scheme: "https", port: 443],
http: [port: 4000]
config :pleroma, :instance,
name: "YourPleromaInstance",
email: "admin@yourdomain.com",
notify_email: "notify@yourdomain.com",
limit: 5000,
registrations_open: false
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "pleroma",
password: "your_secure_password_here",
database: "pleroma",
hostname: "db"
Directory structure (this folder)
your_project/
├── docker-compose.yaml
├── pleroma_backend/
│ └── Dockerfile
├── pleroma_frontend/
│ └── Dockerfile
├── config/
│ └── config.exs
├── postgres_data/ (auto-created)
├── uploads/ (auto-created)
└── static/ (auto-created)
Running
- Initialize DB:
docker-compose up -d db && docker exec -it pleroma_db psql -U pleroma -c "CREATE EXTENSION IF NOT EXISTS citext;
- Start build:
docker-compose up -d --build
- Access localhost:
http://localhost:4000
Optional
Add nginx reverse proxy
Updated docker-compose.yaml
version: "3.8"
services:
db: # (unchanged)
pleroma_backend: # (remove ports: "4000:4000")
pleroma_frontend: # (unchanged)
nginx:
image: nginx:alpine
container_name: pleroma_nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./certs:/etc/nginx/certs
- ./static:/var/lib/pleroma/static:ro
depends_on:
- pleroma_backend
certbot:
image: certbot/certbot
container_name: pleroma_certbot
volumes:
- ./certs:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --post-hook \"docker exec pleroma_nginx nginx -s reload\"; sleep 12h; done;'"
depends_on:
- nginx
Nginx config (nginx/conf.d/pleroma.conf)
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/nginx/certs/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/yourdomain.com/privkey.pem;
location / {
proxy_pass http://pleroma_backend:4000;
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;
}
location /websocket {
proxy_pass http://pleroma_backend:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Certificate setup
- Start nginx:
docker-compose up -d nginx
- Get certs:
docker-compose run --rm certbot certonly --webroot -w /var/www/certbot -d yourdomain.com
- Restart container:
docker-compose restart nginx
Persistent volumes and Cloudflared
Persistent volumes (docker-compose.yaml)
version: "3.8"
services:
db: # (unchanged)
pleroma_backend: # (unchanged)
pleroma_frontend:
# (unchanged except volume renamed to ./pleroma_frontend_dist:/app/dist)
Migration
- Stop container
docker-compose down
- Compress
tar -czf pleroma_backup.tar.gz postgres_data uploads static config docker-compose.yaml pleroma_backend pleroma_frontend pleroma_frontend_dist
- Transfer and unzip on new server
tar -xzf pleroma_backup.tar.gz
- Start and build
docker-compose up -d --build
Cloudflared docker-compose.yaml
version: "3.8"
services:
db: # (unchanged)
pleroma_backend: # (remove ports if using internal network only)
pleroma_frontend: # (unchanged)
cloudflared:
image: cloudflare/cloudflared:latest
container_name: pleroma_cloudflared
restart: always
command: "tunnel --no-autoupdate run --token YOUR_CLOUDFLARED_TOKEN"
depends_on:
- pleroma_backend
Cloudflared setup
- Install
sudo apt install cloudflared
- Login
cloudflared login
- Create tunnel
cloudflared tunnel create pleroma-tunnel
- Congfig
~/.cloudflared/config.yml
- Add CNAME in Cloudflared
yourdomain.com -> <tunnel-uuid>.cfargotunnel.com
- Run
docker-compose up -d --build
Host-based Cloudflared
If cloudflared runs on the host system
docker-compose.yaml
version: "3.8"
services:
db: # (unchanged)
pleroma_backend:
# (unchanged, add ports: - "4000:4000")
pleroma_frontend: # (unchanged)
Config
- Update cloudflared config
ingress:
- hostname: yourdomain.com
service: http://localhost:4000
- Start
docker-compose up -d --build
Advanced featured
Custom network (e.g. 172.24.0.0/16)
version: "3.8"
services:
db:
# (unchanged)
networks:
pleroma_net:
ipv4_address: 172.24.0.2
pleroma_backend:
# (unchanged)
networks:
pleroma_net:
ipv4_address: 172.24.0.3
pleroma_frontend:
# (unchanged)
networks:
pleroma_net:
ipv4_address: 172.24.0.4
networks:
pleroma_net:
driver: bridge
ipam:
config:
- subnet: 172.24.0.0/16
gateway: 172.24.0.1
Updating frontend/backend
- Stop container
docker-compose stop pleroma_backend
ordocker-compose stop pleroma_frontend
- Update
cd pleroma_backend && git pull origin main
(or editDockerfile
)cd pleroma_frontend && git pull origin main
- Rebuild
docker-compose up -d --build pleroma_backend
ordocker-compose up -d --build pleroma_frontend
Monitorin Oban queues
- Admin UI:
https://yourdomain.com/pleroma/admin
-> "Oban Jobs" - DB Query:
docker exec -it pleroma_db psql -U pleroma -d pleroma SELECT state, queue, worker, args, inserted_at FROM oban_jobs ORDER BY inserted_at DESC LIMIT 100;
Set RAM limit to containers
services:
db:
# (unchanged)
mem_limit: 512m
pleroma_backend:
# (unchanged)
mem_limit: 1g
pleroma_frontend:
# (unchanged)
mem_limit: 256m
Final docker-compose.yaml
version: "3.8"
services:
db:
image: postgres:15-alpine
container_name: pleroma_db
restart: always
environment:
POSTGRES_USER: pleroma
POSTGRES_PASSWORD: your_secure_password_here
POSTGRES_DB: pleroma
volumes:
- ./postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "pleroma"]
interval: 10s
timeout: 5s
retries: 5
mem_limit: 512m
networks:
pleroma_net:
ipv4_address: 172.24.0.2
pleroma_backend:
build:
context: ./pleroma_backend
dockerfile: Dockerfile
container_name: pleroma_backend
restart: always
ports:
- "4000:4000"
environment:
DOMAIN: yourdomain.com
INSTANCE_NAME: YourPleromaInstance
ADMIN_EMAIL: admin@yourdomain.com
NOTIFY_EMAIL: notify@yourdomain.com
DB_USER: pleroma
DB_PASS: your_secure_password_here
DB_NAME: pleroma
volumes:
- ./uploads:/var/lib/pleroma/uploads
- ./static:/var/lib/pleroma/static
- ./config:/etc/pleroma
depends_on:
db:
condition: service_healthy
command: ["/bin/sh", "-c", "mix ecto.migrate && ./bin/pleroma start"]
mem_limit: 1g
networks:
pleroma_net:
ipv4_address: 172.24.0.3
pleroma_frontend:
build:
context: ./pleroma_frontend
dockerfile: Dockerfile
container_name: pleroma_frontend
restart: always
volumes:
- ./pleroma_frontend_dist:/app/dist
mem_limit: 256m
networks:
pleroma_net:
ipv4_address: 172.24.0.4
networks:
pleroma_net:
driver: bridge
ipam:
config:
- subnet: 172.24.0.0/16
gateway: 172.24.0.1
Bonus: using a .env file
.env file
# .env
# Domain settings
DOMAIN=yourdomain.com
INSTANCE_NAME=YourPleromaInstance
# Database credentials
DB_USER=pleroma
DB_PASS=your_secure_db_password
DB_NAME=pleroma
# Admin credentials
ADMIN_EMAIL=admin@yourdomain.com
NOTIFY_EMAIL=notify@yourdomain.com
# Cloudflared credentials (if using Cloudflared container)
CLOUDFLARED_TOKEN=your_cloudflared_token
Updated docker-compose.yaml
version: "3.8"
services:
db:
image: postgres:15-alpine
container_name: pleroma_db
restart: always
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_DB: ${DB_NAME}
volumes:
- ./postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
mem_limit: 512m
networks:
pleroma_net:
ipv4_address: 172.24.0.2
pleroma_backend:
build:
context: ./pleroma_backend
dockerfile: Dockerfile
container_name: pleroma_backend
restart: always
ports:
- "4000:4000"
environment:
DOMAIN: ${DOMAIN}
INSTANCE_NAME: ${INSTANCE_NAME}
ADMIN_EMAIL: ${ADMIN_EMAIL}
NOTIFY_EMAIL: ${NOTIFY_EMAIL}
DB_USER: ${DB_USER}
DB_PASS: ${DB_PASS}
DB_NAME: ${DB_NAME}
volumes:
- ./uploads:/var/lib/pleroma/uploads
- ./static:/var/lib/pleroma/static
- ./config:/etc/pleroma
depends_on:
db:
condition: service_healthy
command: ["/bin/sh", "-c", "mix ecto.migrate && ./bin/pleroma start"]
mem_limit: 1g
networks:
pleroma_net:
ipv4_address: 172.24.0.3
pleroma_frontend:
build:
context: ./pleroma_frontend
dockerfile: Dockerfile
container_name: pleroma_frontend
restart: always
volumes:
- ./pleroma_frontend_dist:/app/dist
mem_limit: 256m
networks:
pleroma_net:
ipv4_address: 172.24.0.4
networks:
pleroma_net:
driver: bridge
ipam:
config:
- subnet: 172.24.0.0/16
gateway: 172.24.0.1
Changes:
- Environment Variables: Replaced hardcoded values in the environment sections with
${VARIABLE_NAME}
(e.g.,${DOMAIN}
,${DB_USER}
). - Healthcheck: Updated
pg_isready
to use${DB_USER}
dynamically. - Cloudflared: If you were using the
cloudflared
service (not included here since if running it on the host), you’d add:cloudflared: image: cloudflare/cloudflared:latest container_name: pleroma_cloudflared restart: always command: "tunnel --no-autoupdate run --token ${CLOUDFLARED_TOKEN}" depends_on: - pleroma_backend
- For host-based Cloudflared, no changes are needed here since it’s managed outside Docker.
Update config.exs
for pleroma
The Pleroma configuration file (config/config.exs
) needs to read environment variables at runtime. Elixir’s System.get_env/1
function can fetch these variables, so we’ll modify config.exs
to use them.
use Mix.Config
# Domain and endpoint settings
config :pleroma, Pleroma.Web.Endpoint,
url: [host: System.get_env("DOMAIN"), scheme: "https", port: 443],
http: [port: 4000]
# Instance settings
config :pleroma, :instance,
name: System.get_env("INSTANCE_NAME"),
email: System.get_env("ADMIN_EMAIL"),
notify_email: System.get_env("NOTIFY_EMAIL"),
limit: 5000,
registrations_open: false
# Database settings
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("DB_USER"),
password: System.get_env("DB_PASS"),
database: System.get_env("DB_NAME"),
hostname: "db"
Changes Explained
- Replaced static values with
System.get_env/1
calls (e.g.,System.get_env("DOMAIN")
). - The variables (
DOMAIN
,DB_USER
, etc.) match those in.env
and are passed into thepleroma_backend
container via theenvironment
section indocker-compose.yaml
.
Host-based cloudflared config
If you’re running cloudflared on the host, its configuration (~/.cloudflared/config.yml
) isn’t directly managed by Docker Compose. However, you can still reference the domain from the .env
file manually when setting it up.
Updated ~/.cloudflared/config.yml