Українська
Почати
← Усі шаблони

Mastodon на Pethost в один клік

Свій сервер у федиверсі: дописи, підписки й відповіді по всьому Mastodon і не лише

Що відбудеться

  1. Ви входите в Pethost або створюєте акаунт — з ключем доступу, через GitHub або Google.
  2. Машини ще немає? Ви обираєте тариф, і ми готуємо машину для вас. Посилання чекає у вашому браузері, доки машина не буде готова.
  3. Вікно «Новий проєкт» відкривається на цьому застосунку. Ви заповнюєте те, що він просить, і натискаєте «Створити проєкт»: Ім’я користувача, Електронна пошта, Пароль адміністратора.
  4. За хвилину-дві застосунок працює за власною адресою з HTTPS. Його секрети створюються випадково, а дані потрапляють у щонічний бекап.
  5. Це проєкт як будь-який інший: його compose.yaml, .env, логи й термінал — у панелі, а ваш агент може його змінити, відкотити або відновити.

Перші кроки

Увійдіть із поштою та паролем, які ви вказали: ви власник сервера, з тим ім’ям користувача, яке ви вказали. Пошта має приймати листи: Mastodon перевіряє її домен, інакше деплой не вдасться. Імена на кшталт admin або root Mastodon резервує. Реєстрацію закрито; відкрийте її в Налаштуваннях, Адміністрування, Налаштування сервера. Mastodon не надсилає листів, доки SMTP_SERVER, SMTP_LOGIN, SMTP_PASSWORD і SMTP_FROM_ADDRESS не з’являться в .env проєкту і проєкт не перезапуститься. Кожен акаунт назавжди прив’язаний до цієї адреси, тож дайте проєкту остаточну адресу до перших дописів. Завантажені медіафайли зберігаються в /opt/mastodon/public/system.

Що запускається

Шаблон — це цей compose.yaml і змінні, які він записує в .env проєкту. Pethost запускає файл так, як його написано.

# Mastodon's docker-compose.yml with a small Caddy in front, which gives the streaming
# API its path on the project's address, as Mastodon's nginx does.
x-env: &env
  LOCAL_DOMAIN: ${HOST}
  DB_HOST: db
  DB_PORT: "5432"
  DB_USER: mastodon
  DB_NAME: mastodon
  DB_PASS: ${DB_PASSWORD}
  REDIS_HOST: redis
  REDIS_PORT: "6379"
  SECRET_KEY_BASE: ${SECRET_KEY_BASE}
  ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: ${AR_DETERMINISTIC_KEY}
  ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: ${AR_KEY_DERIVATION_SALT}
  ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: ${AR_PRIMARY_KEY}
  VAPID_SEED: ${VAPID_SEED}
  ES_ENABLED: "false"
  WEB_CONCURRENCY: "1"
  SMTP_SERVER: ${SMTP_SERVER:-}
  SMTP_PORT: ${SMTP_PORT:-587}
  SMTP_LOGIN: ${SMTP_LOGIN:-}
  SMTP_PASSWORD: ${SMTP_PASSWORD:-}
  SMTP_FROM_ADDRESS: ${SMTP_FROM_ADDRESS:-}

# Web push needs a VAPID key pair. Its private key is the 32 random bytes of VAPID_SEED,
# and the public key is derived from it, so web and sidekiq read the same pair and
# nothing is kept apart from .env.
x-vapid: &vapid |
  eval "$$(ruby -ropenssl -rbase64 -e '
    d = [ENV.fetch("VAPID_SEED")].pack("H*")
    point = OpenSSL::PKey::EC::Group.new("prime256v1").generator.mul(OpenSSL::BN.new(d, 2))
    puts "export VAPID_PRIVATE_KEY=#{Base64.urlsafe_encode64(d)}"
    puts "export VAPID_PUBLIC_KEY=#{Base64.urlsafe_encode64(point.to_octet_string(:uncompressed))}"
  ')"

services:
  proxy:
    image: caddy:2.10-alpine
    restart: unless-stopped
    configs:
      - source: caddy
        target: /etc/caddy/Caddyfile
    depends_on:
      - web
      - streaming

  web:
    image: ghcr.io/mastodon/mastodon:v4.7.3
    restart: unless-stopped
    environment:
      <<: *env
      ADMIN_USERNAME: ${ADMIN_USERNAME}
      ADMIN_EMAIL: ${ADMIN_EMAIL}
      ADMIN_PASSWORD: ${ADMIN_PASSWORD}
      VAPID: *vapid
    # Every start migrates the database and seeds what is missing; the first one also
    # makes the owner from the username, email and password given. Registrations stay
    # closed, Mastodon's default, so nobody else can sign up until the owner opens them.
    command:
      - sh
      - -c
      - |
        set -e
        eval "$$VAPID"
        bundle exec rails db:prepare db:seed
        bundle exec rails runner '
          owner = UserRole.find_by!(name: "Owner")
          unless User.exists?(role_id: owner.id)
            password = ENV.fetch("ADMIN_PASSWORD")
            user = User.create!(
              email: ENV.fetch("ADMIN_EMAIL"),
              password: password,
              password_confirmation: password,
              account: Account.new(username: ENV.fetch("ADMIN_USERNAME")),
              role: owner,
              confirmed_at: Time.now.utc,
              agreement: true,
              approved: true,
              bypass_registration_checks: true
            )
            # Closed registrations leave a new user waiting for approval.
            user.approve!
          end
        '
        exec bundle exec puma -C config/puma.rb
    volumes:
      - system:/opt/mastodon/public/system
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -s --noproxy localhost localhost:3000/health | grep -q OK"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 300s

  streaming:
    image: ghcr.io/mastodon/mastodon-streaming:v4.7.3
    restart: unless-stopped
    environment: *env
    command: node ./streaming/index.js
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -s --noproxy localhost localhost:4000/api/v1/streaming/health | grep -q OK"]
      interval: 10s
      timeout: 5s
      retries: 5

  sidekiq:
    image: ghcr.io/mastodon/mastodon:v4.7.3
    restart: unless-stopped
    environment:
      <<: *env
      VAPID: *vapid
    command:
      - sh
      - -c
      - |
        eval "$$VAPID"
        exec bundle exec sidekiq
    volumes:
      - system:/opt/mastodon/public/system
    depends_on:
      web:
        condition: service_healthy

  db:
    image: postgres:17
    restart: unless-stopped
    shm_size: 256mb
    environment:
      POSTGRES_USER: mastodon
      POSTGRES_DB: mastodon
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "mastodon"]
      interval: 5s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7.4
    restart: unless-stopped
    volumes:
      - redis:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 10

configs:
  # The machine's proxy is trusted for X-Forwarded-Proto, so Rails sees HTTPS.
  caddy:
    content: |
      {
        admin off
        auto_https off
        servers {
          trusted_proxies static private_ranges
        }
      }

      :8080 {
        reverse_proxy /api/v1/streaming* streaming:4000
        reverse_proxy web:3000
      }

volumes:
  system:
  db:
  redis: