# 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: