1. Setup nginx-proxy

We’ll use nginx-proxy and its acme companion to setup a reverse proxy that handles mutliple web services.

First, create the nginx-proxy network:

docker create network nginx-proxy

Option 1.1 - use nginx-proxy and its acme companion

Then setup the docker-compose.yml in a nginx-proxy folder.

services:
  nginx-proxy:
    image: nginxproxy/nginx-proxy
    container_name: nginx-proxy
    labels:
      - com.github.nginx-proxy.nginx_proxy=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./volumes/certs:/etc/nginx/certs
      - ./volumes/conf.d:/etc/nginx/conf.d
      - ./volumes/vhost:/etc/nginx/vhost.d
      - ./volumes/html:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    dns:
      - 8.8.8.8
      - 8.8.4.4
    restart: always

  nginx-proxy-acme:
    image: nginxproxy/acme-companion
    container_name: nginx-proxy-acme
    depends_on:
      - nginx-proxy
    volumes_from:
      - nginx-proxy
    volumes:
      - ./volumes/certs:/etc/nginx/certs:rw
      - ./volumes/acme:/etc/acme.sh
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      - DEFAULT_EMAIL=YOUR_EMAIL_HERE_FOR_LETSENCRYPT_CERTS
      - NGINX_PROXY_CONTAINER=nginx-proxy
    restart: always

networks:
  default:
    name: nginx-proxy
    external: true

Option 1.2 - nginx-proxy-manager

This a graphical tool that provides a handy webUI and an embedded certificate manager. It may be a better investment if you plan to have multiple services.

---
services:
  nginx-proxy-manager:
    image: 'docker.io/jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
      - '8081:81'
    volumes:
      - ./volumes/npm/npm_data:/data
      - ./volumes/npm/letsencrypt:/etc/letsencrypt

networks:
  default: 
    name: nginx-proxy
    external: true

2. Setup mastodon

In a dedicated mastodon folder, prepare the following docker-compose.yml (see also official instructions). You will want to use elasticsearch for full site search. Make sure your resources allow it.

# Adapted from <https://github.com/mastodon/mastodon/blob/main/docker-compose.yml>
---
services:
  db:
    restart: always
    image: postgres:14-alpine
    shm_size: 256mb
    networks:
      - internal_network
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'postgres']
    volumes:
      - ./postgres14:/var/lib/postgresql/data
    environment:
      - 'POSTGRES_HOST_AUTH_METHOD=trust'

  redis:
    restart: always
    image: redis:7-alpine
    networks:
      - internal_network
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
    volumes:
      - ./redis:/data

  es:
    restart: always
    image: elasticsearch:7.17.24
    environment:
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
      - "xpack.license.self_generated.type=basic"
      - "xpack.security.enabled=false"
      - "xpack.watcher.enabled=false"
      - "xpack.graph.enabled=false"
      - "xpack.ml.enabled=false"
      - "bootstrap.memory_lock=true"
      - "cluster.name=es-mastodon"
      - "discovery.type=single-node"
      - "thread_pool.write.queue_size=1000"
    networks:
       - external_network
       - internal_network
    healthcheck:
       test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
    volumes:
       - ./elasticsearch:/usr/share/elasticsearch/data
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    ports:
      - '127.0.0.1:9200:9200'

  web:
    #build: .
    image: tootsuite/mastodon:${MASTODON_VERSION}
    #image: ghcr.io/mastodon/mastodon:v4.3.0 this would be the reccomended image on the github docker-compose file
    restart: always
    env_file: .env.production
    #command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    command: bundle exec puma -C config/puma.rb
    networks:
      - external_network
      - internal_network
    healthcheck:
      # prettier-ignore
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
    ports:
      - '127.0.0.1:3000:3000'
    depends_on:
      - db
      - redis
      - es
    volumes:
      - ./public/system:/mastodon/public/system
    expose:
      - 3000
    environment:
      VIRTUAL_HOST: "${MASTODON_DOMAIN}"
      LETSENCRYPT_HOST: "${MASTODON_DOMAIN}"
      VIRTUAL_PATH: "/"
      VIRTUAL_PORT: 3000
      RAILS_LOG_LEVEL: warn

  streaming:
    #build: .
    image: tootsuite/mastodon-streaming:${MASTODON_VERSION}
    restart: always
    env_file: .env.production
#    command: node ./streaming
    command: node ./streaming/index.js
    networks:
      - external_network
      - internal_network
    healthcheck:
      # prettier-ignore
      test: ['CMD-SHELL', "curl -s --noproxy localhost localhost:4000/api/v1/streaming/health | grep -q 'OK' || exit 1"]
    ports:
      - '127.0.0.1:4000:4000'
    depends_on:
      - db
      - redis
    expose:
      - 4000
    environment:
      VIRTUAL_HOST: "${MASTODON_DOMAIN}"
      VIRTUAL_PATH: "/api/v1/streaming"
      VIRTUAL_PORT: 4000

  sidekiq:
    #build: .
    image: tootsuite/mastodon:${MASTODON_VERSION}
    restart: always
    env_file: .env.production
    command: bundle exec sidekiq
    environment:
      - RAILS_LOG_LEVEL=error
    depends_on:
      - db
      - redis
    networks:
      - external_network
      - internal_network
    volumes:
      - ./public/system:/mastodon/public/system
    healthcheck:
      test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\\ 6' || false"]

  ## Uncomment to enable federation with tor instances along with adding the following ENV variables
  ## http_hidden_proxy=http://privoxy:8118
  ## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
  # tor:
  #   image: sirboops/tor
  #   networks:
  #      - external_network
  #      - internal_network
  #
  # privoxy:
  #   image: sirboops/privoxy
  #   volumes:
  #     - ./priv-config:/opt/config
  #   networks:
  #     - external_network
  #     - internal_network

networks:
  external_network:
    name: nginx-proxy
    external: true
  internal_network:
    internal: true

Then add some custom info regarding your instance.

.env

#.env
[email protected]
MASTODON_DOMAIN=mymastodon.com
MASTODON_VERSION=v4.3.3

Launch the docker machinery and autocreate the env.production file.

# first time: setup mastodon
touch .env.production
sudo docker-compose run --rm -u root -v $(pwd)/.env.production:/opt/mastodon/.env.production -e RUBYOPT=-W0 web bundle exec rake mastodon:setup

# launch mastodon
docker-compose up -d

A new autocreated file from /opt/mastodon/.env.production and mapped locally contains all the important configurations settings. It can be modified if needed, and the server can be restarted at anytime with docker compose up -d