pleroma-docker/README.md
2025-03-19 14:56:09 -04:00

15 KiB
Raw Permalink Blame History

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 doesnt have an official Docker image, so we build it from source. Heres 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

  1. Initialize DB: docker-compose up -d db && docker exec -it pleroma_db psql -U pleroma -c "CREATE EXTENSION IF NOT EXISTS citext;
  2. Start build: docker-compose up -d --build
  3. 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

  1. Start nginx: docker-compose up -d nginx
  2. Get certs: docker-compose run --rm certbot certonly --webroot -w /var/www/certbot -d yourdomain.com
  3. 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

  1. Stop container docker-compose down
  2. Compress tar -czf pleroma_backup.tar.gz postgres_data uploads static config docker-compose.yaml pleroma_backend pleroma_frontend pleroma_frontend_dist
  3. Transfer and unzip on new server tar -xzf pleroma_backup.tar.gz
  4. 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

  1. Install sudo apt install cloudflared
  2. Login cloudflared login
  3. Create tunnel cloudflared tunnel create pleroma-tunnel
  4. Congfig ~/.cloudflared/config.yml
  5. Add CNAME in Cloudflared yourdomain.com -> <tunnel-uuid>.cfargotunnel.com
  6. 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

  1. Update cloudflared config
ingress:
  - hostname: yourdomain.com
    service: http://localhost:4000
  1. Start docker-compose up -d --build

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

  1. Stop container docker-compose stop pleroma_backend or docker-compose stop pleroma_frontend
  2. Update
    1. cd pleroma_backend && git pull origin main (or edit Dockerfile)
    2. cd pleroma_frontend && git pull origin main
  3. Rebuild docker-compose up -d --build pleroma_backend or docker-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), youd 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 its managed outside Docker.

Update config.exs for pleroma

The Pleroma configuration file (config/config.exs) needs to read environment variables at runtime. Elixirs System.get_env/1 function can fetch these variables, so well 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 the pleroma_backend container via the environment section in docker-compose.yaml.

Host-based cloudflared config

If youre running cloudflared on the host, its configuration (~/.cloudflared/config.yml) isnt 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