How to Install Mastodon Social Network with Docker on Ubuntu 22.04 LTS

Mastodon is free and open-source software for running self-hosted social networking services. It's a decentralized social network made up of independent servers organized around specific themes, topics, or interests.

Prerequisites

  • A system with Ubuntu 22.04 and root access.
  • Server requires a minimum of 2 CPU cores and 2GB of memory.
  • Users receive email notifications from Mastodon. Using a third-party transactional mail service like Mailgun, Sendgrid, Amazon SES, or Sparkpost is something we strongly advise. Amazon SES will be used in the instructions in this wiki.

Update the System

Update the system to the latest with the following commands,

apt update

apt upgrade

Install some of the basic utility packages that would be used later in the installation,

apt install wget curl nano software-properties-common dirmngr apt-transport-https gnupg gnupg2 ca-certificates lsb-release ubuntu-keyring unzip -y

Configure Firewall

We will now enable Firewall and allow HTTP and HTTPS ports which are needed to allow public connection to the site.

ufw status

Output:

root@vps:~# ufw status
Status: inactive

Since the UFW is inactive, we will enable the firewall. And at the same time, we would also need to allow port for OpenSSH so we don't loose our SSH connection to the server.

This can be done with the following command,

ufw allow OpenSSH

Allow both HTTP and HTTPS ports,

ufw allow http
ufw allow https

Enable the Firewall, press Y key when prompted for confirmation,

ufw enable

Output:

root@vps:~# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? Y
Firewall is active and enabled on system startup

Now, Check the status of the firewall again it should be active now

ufw status

Output:

root@vps:~# ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443                        ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)

Install Docker and Docker Compose

Docker is shipped with an outdated version in Ubuntu 22.04. Import the Docker GPG key first before installing the most recent version.

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

Create Docker repository file.

echo   "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Update the system repository list.

apt update

Install the latest version of Docker,

apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Preparing for Installation

For Elasticsearch, the mmap counts have a relatively low default limit. Run the next command to verify the default setting,

sysctl vm.max_map_count

output:

root@vps:sysctl vm.max_map_count
vm.max_map_count = 65530

The following commands can be used to increase mmap numbers,

echo "vm.max_map_count=262144" | sudo tee /etc/sysctl.d/90-max_map_count.conf
sysctl --load /etc/sysctl.d/90-max_map_count.conf

Install Mastodon

Make Directories and Configure Ownerships

Create directories for Mastodon and related services,

mkdir -p /opt/mastodon/database/{postgresql,pgbackups,redis,elasticsearch}
mkdir -p /opt/mastodon/web/{public,system}
mkdir -p /opt/mastodon/branding

access to the web, backup, and elasticsearch directories,

chown 991:991 /opt/mastodon/web/{public,system}
chown 1000 /opt/mastodon/database/elasticsearch
chown 70:70 /opt/mastodon/database/pgbackups

Now, Switch to the Mastodon directory,

cd /opt/mastodon

Create environment and docker compose files

Create an empty environment for the application and database,

touch application.env database.env

Create and open the Docker compose file for editing.

nano docker-compose.yml

Add the following content into Docker compose file docker-compose.yml file and save it.

version: '3'

services:
  postgresql:
    image: postgres:15-alpine
    env_file: database.env
    restart: always
    shm_size: 512mb
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'postgres']
    volumes:
      - postgresql:/var/lib/postgresql/data
      - pgbackups:/backups
    networks:
      - internal_network

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

  redis-volatile:
    image: redis:7-alpine
    restart: always
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
    networks:
      - internal_network

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.7
    restart: always
    env_file: database.env
    environment:
      - cluster.name=elasticsearch-mastodon
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - xpack.security.enabled=true
      - ingest.geoip.downloader.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
      - xpack.license.self_generated.type=basic
      - xpack.watcher.enabled=false
      - xpack.graph.enabled=false
      - xpack.ml.enabled=false
      - thread_pool.write.queue_size=1000
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    healthcheck:
      test: ["CMD-SHELL", "nc -z elasticsearch 9200"]
    volumes:
      - elasticsearch:/usr/share/elasticsearch/data
    networks:
      - internal_network
    ports:
      - '127.0.0.1:9200:9200'

  website:
    image: tootsuite/mastodon:v4.1.2
    env_file:
      - application.env
      - database.env
    command: bash -c "bundle exec rails s -p 3000"
    restart: always
    depends_on:
      - postgresql
      - redis
      - redis-volatile
      - elasticsearch
    ports:
      - '127.0.0.1:3000:3000'
    networks:
      - internal_network
      - external_network
    healthcheck:
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
    volumes:
      - uploads:/mastodon/public/system

  shell:
    image: tootsuite/mastodon:v4.1.2
    env_file:
      - application.env
      - database.env
    command: /bin/bash
    restart: "no"
    networks:
      - internal_network
      - external_network
    volumes:
      - uploads:/mastodon/public/system
      - static:/static

  streaming:
    image: tootsuite/mastodon:v4.1.2
    env_file:
      - application.env
      - database.env
    command: node ./streaming
    restart: always
    depends_on:
      - postgresql
      - redis
      - redis-volatile
      - elasticsearch
    ports:
      - '127.0.0.1:4000:4000'
    networks:
      - internal_network
      - external_network
    healthcheck:
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']

  sidekiq:
    image: tootsuite/mastodon:v4.1.2
    env_file:
      - application.env
      - database.env
    command: bundle exec sidekiq
    restart: always
    depends_on:
      - postgresql
      - redis
      - redis-volatile
      - website
    networks:
      - internal_network
      - external_network
    healthcheck:
      test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
    volumes:
      - uploads:/mastodon/public/system

networks:
  external_network:
  internal_network:
  internal:true:

volumes:
  postgresql:
    driver_opts:
      type: none
      device: /opt/mastodon/database/postgresql
      o: bind
  pgbackups:
    driver_opts:
      type: none
      device: /opt/mastodon/database/pgbackups
      o: bind
  redis:
    driver_opts:
      type: none
      device: /opt/mastodon/database/redis
      o: bind
  elasticsearch:
    driver_opts:
      type: none
      device: /opt/mastodon/database/elasticsearch
      o: bind
  uploads:
    driver_opts:
      type: none
      device: /opt/mastodon/web/system
      o: bind
  static:
    driver_opts:
      type: none
      device: /opt/mastodon/web/public
      o: bind

Mastodon's most recent release as of the time the guide was written was v4.1.2.
Check the Mastodon GitHub Releases page and make the necessary adjustments to the version in the Docker compose file. https://github.com/mastodon/mastodon/releases
Redis and PostgreSQL are both running at the most recent versions. They can be modified to meet your needs.
Currently, we are utilising Elasticsearch 7.x. Elasticsearch does not have a major version that you can follow on the Docker Hub website, therefore you will need to constantly manually updating it for Java security patches.

Create Application Secrets

Generate SECRET_KEY_BASE and OTP_SECRET values by running the following command twice. The first time will take some time as it will pull the images,

docker compose run --rm shell bundle exec rake secret

Run this commands for SSL utility,

 openssl rand -hex 64

Generate VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY values by using the following command,

docker compose run --rm shell bundle exec rake mastodon:webpush:generate_vapid_key 

Output:

VAPID_PRIVATE_KEY=fxkMuPKbR1LR2WQEGhPV7_NiE3WqnAzoJ2VC8C-6BoA=
VAPID_PUBLIC_KEY=BF_LJDJJyF-Rhji9OvIqErui2S_EpaAG9bn_TyMD3Jmqzz8ce5KJE7apfxZ0Cm6cR-_kTBxdjC90LaqNJClKBAw=

The openssl utility to generate PostgreSQL and Elasticsearch passwords.

openssl rand -hex 15

Mastodon Environment Files

Edit the application.env file,

nano application.env

Add the following lines of code into application.env file and save it.

# environment
RAILS_ENV=production
NODE_ENV=production

# domain
LOCAL_DOMAIN=mastodon.example.com

# redirect to the first profile
SINGLE_USER_MODE=false

# do not serve static files
RAILS_SERVE_STATIC_FILES=false

# concurrency
WEB_CONCURRENCY=2
MAX_THREADS=5

# pgbouncer
#PREPARED_STATEMENTS=false

# locale
DEFAULT_LOCALE=en

# email, not used
SMTP_SERVER=email-smtp.us-west-2.amazonaws.com
SMTP_PORT=587
SMTP_LOGIN=AES_USER
SMTP_PASSWORD=AES_PWD
SMTP_FROM_ADDRESS=noreply@example.com

# secrets
SECRET_KEY_BASE=c09fa403575e0b431e54a2e228f20cd5a5fdfdbba0da80598959753b829a4e3c0266eedbac7e3cdf9f3345db36c56302c0e1bc5bfc8c5d516be59a2c41de7e37
OTP_SECRET=febb7dbb0d3308094083733fc923a430e52ccec767d48d7d2e0c577bfcb6863dbdfc920b1004b1f8c2967b9866bd7a0b4a15460f9fc7687aa4a42acf54e5a3d4

# Changing VAPID keys will break push notifications
VAPID_PRIVATE_KEY=13RgrfOY2tkwuUycylDPOkoHennkJ0ZAPV_fUwDy7-g=
VAPID_PUBLIC_KEY=BDAQuGwPbh1kbCV904adYXHvz9lLRaJHkiQkihRDPyBn3QmkAYbR21WHYoP8TkyG6dylG6IXpEVfLwdoW7fJVns=

# IP and session retention
# -----------------------
# Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml
# to be less than daily if you lower IP_RETENTION_PERIOD below two days (172800).
# -----------------------
IP_RETENTION_PERIOD=2592000
SESSION_RETENTION_PERIOD=2592000

LOCAL_DOMAIN -> Provide the actual domain name that you want the site on.
SECRET_KEY_BASE - OTP_SECRET - VAPID_PRIVATE_KEY - VAPID_PUBLIC_KEY -> Replace the keys with the newly generated keys from the previous step.
SMTP -> For the email settings, provide the SMTP logins from Amazon SES account. (you can leave it empty if you do not have an SMTP account)

Edit the database.env file,

nano database.env

Add the following lines of code into database.env file and save it.

# postgresql configuration
POSTGRES_USER=mastodon
POSTGRES_DB=mastodon
POSTGRES_PASSWORD=v9MPbqBYrA6VNEpfv9MPbqBYrA6VNEpf
PGPASSWORD=v9MPbqBYrA6VNEpfv9MPbqBYrA6VNEpf
PGPORT=5432
PGHOST=postgresql
PGUSER=mastodon

# pgbouncer configuration
#POOL_MODE=transaction
#ADMIN_USERS=postgres,mastodon
#DATABASE_URL="postgres://mastodon:v9MPbqBYrA6VNEpfv9MPbqBYrA6VNEpf@postgresql:5432/mastodon"

# elasticsearch
ES_JAVA_OPTS=-Xms512m -Xmx512m
ELASTIC_PASSWORD=cUzQVpD4Qr8wpt1fcUzQVpD4Qr8wpt1f

# mastodon database configuration
#DB_HOST=pgbouncer
DB_HOST=postgresql
DB_USER=mastodon
DB_NAME=mastodon
DB_PASS=v9MPbqBYrA6VNEpfv9MPbqBYrA6VNEpf
DB_PORT=5432

REDIS_HOST=redis
REDIS_PORT=6379

CACHE_REDIS_HOST=redis-volatile
CACHE_REDIS_PORT=6379

ES_ENABLED=true
ES_HOST=elasticsearch
ES_PORT=9200
ES_USER=elastic
ES_PASS=cUzQVpD4Qr8wpt1fcUzQVpD4Qr8wpt1f

In this configuration, we're creating the Database and related settings for it.
The Database name, username and password are all newly created using the above configuration.
Replace the above mentioned password of your choice and ensure it's strong and long.

Mastodon preparation

Prepare the static files so that Nginx can serve them. Because Docker will be pulling all the images for the first time during this stage, it will take some time,

docker compose run --rm shell bash -c "cp -r /opt/mastodon/public/* /static/"

open the data layer.

docker compose up -d postgresql redis redis-volatile

Verify the containers' state,

watch docker compose ps

When the database is running (healthy), hit Ctrl + C and use the following command to initialise it.

docker compose run --rm shell bundle exec rake db:setup

Install Ngnix

Download and install the most recent version from the official Nginx repository.

Import the Nginx GPG key,

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

Add the repository for Nginx's stable version.

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg arch=amd64] \http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | sudo tee /etc/apt/sources.list.d/nginx.list

Then, update the system's apt repositories,

apt update

Install Nginx using the apt package manager,

apt install nginx

Check the version

nginx -v

Start and Enable Nginx,

systemctl start nginx

systemctl enable nginx

Install Let’s Encrypt SSL Certificate

Let's issue an SSL certificate for the domain, for this we will need a snap package for Ubuntu operating system.

We will update and install the snap package on the system:

apt update

apt install -y snapd

snap install core && sudo snap refresh core

Next, with the help of snap, we will install the certbot client which is used to create Let's Encrypt certificates:

snap install --classic certbot

ln -s /snap/bin/certbot /usr/bin/certbot

Install SSL Certificate:

Use the certbot command to issue a Let's Encrypt certificate. The command will auto-detect the domains available or configured in the vHost configuration:

certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m name@example.com -d dev.domainhere.info

Replace name@example.com with your email address and the domain dev.domainhere.info with the actual domain.

Generate a Diffie-Hellman group certificate,

openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096

To check whether the SSL renewal is working fine, do a dry run of the process.

certbot renew --dry-run

If you see no errors, you are all set. Your certificate will renew automatically.

Setting up Nginx

Edit the file /etc/nginx/nginx.conf

nano /etc/nginx/nginx.conf

Before the line include /etc/nginx/conf.d/*.conf in /etc/nginx/nginx.conf add the following lines of code,

server_names_hash_bucket_size  64;

Open the file /etc/nginx/conf.d/mastodon.conf for edit,

nano /etc/nginx/conf.d/mastodon.conf

Add the Following lines of code into /etc/nginx/conf.d/mastodon.conf file and save it.

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

upstream backend {
    server 127.0.0.1:3000 fail_timeout=0;
}

upstream streaming {
    server 127.0.0.1:4000 fail_timeout=0;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;

server {
  listen 80 default_server;
  server_name dev.domainhere.info;
  location / { return 301 https://$host$request_uri; }
}

server {
   listen 443 ssl http2;
   server_name dev.domainhere.info;

   access_log  /var/log/nginx/mastodon.access.log;
   error_log   /var/log/nginx/mastodon.error.log;

   http2_push_preload on; # Enable HTTP/2 Server Push

   ssl_certificate /etc/letsencrypt/live/dev.domainhere.info/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/dev.domainhere.info/privkey.pem;
   ssl_trusted_certificate /etc/letsencrypt/live/dev.domainhere.info/chain.pem;
   ssl_session_timeout 1d;

   # Enable TLS versions (TLSv1.3 is required upcoming HTTP/3 QUIC).
   ssl_protocols TLSv1.2 TLSv1.3;

   # Enable TLSv1.3's 0-RTT. Use $ssl_early_data when reverse proxying to
   # prevent replay attacks.
   #
   # @see: https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
   ssl_early_data on;

   ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384';
   ssl_prefer_server_ciphers on;
   ssl_session_cache shared:SSL:10m;
   ssl_session_tickets off;

   keepalive_timeout    70;
   sendfile             on;
   client_max_body_size 80m;

   # OCSP Stapling ---
   # fetch OCSP records from URL in ssl_certificate and cache them
   ssl_stapling on;
   ssl_stapling_verify on;
   ssl_dhparam /etc/ssl/certs/dhparam.pem;

   add_header X-Early-Data $tls1_3_early_data;

   root /opt/mastodon/web/public;

   gzip on;
   gzip_disable "msie6";
   gzip_vary on;
   gzip_proxied any;
   gzip_comp_level 6;
   gzip_buffers 16 8k;
   gzip_http_version 1.1;
   gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon;

   add_header Strict-Transport-Security "max-age=31536000" always;

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Strict-Transport-Security "max-age=31536000" always;
    root /opt/mastodon/;
    try_files $uri @proxy;
  }

  location ~ ^/(emoji|packs) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Strict-Transport-Security "max-age=31536000" always;
    try_files $uri @proxy;
  }

  location /sw.js {
    add_header Cache-Control "public, max-age=0";
    add_header Strict-Transport-Security "max-age=31536000" always;
    try_files $uri @proxy;
  }

  location @proxy {
    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;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://backend;
    proxy_buffering on;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    proxy_cache CACHE;
    proxy_cache_valid 200 7d;
    proxy_cache_valid 410 24h;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    add_header X-Cached $upstream_cache_status;
    add_header Strict-Transport-Security "max-age=31536000" always;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    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;
    proxy_set_header Proxy "";

    proxy_pass http://streaming;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}

# This block is useful for debugging TLS v1.3. Please feel free to remove this
# and use the `$ssl_early_data` variable exposed by NGINX directly should you
# wish to do so.
map $ssl_early_data $tls1_3_early_data {
  "~." $ssl_early_data;
  default "";
}

Replace dev.domainhere.info with actual domain name you are using.

Use to check the Nginx configuration,

nginx -t

Restart the Ngnix Server,

systemctl restart nginx

Start Mastodon

Tootctl CLI tool On Mastodon, management operations are carried by using the Tootctl CLI tool. We must enable host shell access to it,

nano /usr/local/bin/tootctl

Add the following lines into /usr/local/bin/tootctl

#!/bin/bash
docker compose -f /opt/mastodon/docker-compose.yml run --rm shell tootctl "$@"

Grant the file permission,

chmod +x /usr/local/bin/tootctl

Mastodon Service File

The systemd unit file makes starting the Mastodon containers more simpler than using the Docker compose command,

nano /etc/systemd/system/mastodon.service

Add the following lines of code into/etc/systemd/system/mastodon.service

[Unit]
Description=Mastodon service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes

WorkingDirectory=/opt/mastodon
ExecStart=/usr/bin/docker compose -f /opt/mastodon/docker-compose.yml up -d
ExecStop=/usr/bin/docker compose -f /opt/mastodon/docker-compose.yml down

[Install]
WantedBy=multi-user.target

To start the service file, restart the system daemon,

systemctl daemon-reload

Enable and start the Mastodon service,

systemctl enable --now mastodon.service

Check the status of the Docker containers,

watch docker compose -f /opt/mastodon/docker-compose.yml ps

Make an admin user for Mastodon and make a note of the password,

root@vps:/opt/mastodon# tootctl accounts create navjot --email support@gmail.com --confirmed --role Owner
[+] Building 0.0s (0/0)
[+] Building 0.0s (0/0)
OK
New password: c9e34cd759a2c251ae75a75f6c4aed8a

Use the following command to close user registrations,

 tootctl settings registrations close

Issue the following command to reopen the registrations,

tootctl settings registrations open

Initialize Search

Before you can create and populate Elasticsearch indexes, you must toot. Make a toot and then give the following order,

tootctl search deploy

Output:

root@vps:/opt/mastodon# tootctl search deploy
[+] Building 0.0s (0/0)
[+] Building 0.0s (0/0)
/opt/mastodon/vendor/bundle/ruby/3.0.0/gems/ruby-progressbar-1.11.0/lib/ruby-progressbar/progress.rb:76:in `total=': You can't set the item's total value to less than the current progress. (ProgressBar::InvalidProgressError)
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/ruby-progressbar-1.11.0/lib/ruby-progressbar/base.rb:178:in `block in update_progress'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/ruby-progressbar-1.11.0/lib/ruby-progressbar/output.rb:43:in `with_refresh'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/ruby-progressbar-1.11.0/lib/ruby-progressbar/base.rb:177:in `update_progress'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/ruby-progressbar-1.11.0/lib/ruby-progressbar/base.rb:101:in `total='
        from /opt/mastodon/lib/mastodon/search_cli.rb:67:in `deploy'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor/invocation.rb:116:in `invoke'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor.rb:243:in `block in subcommand'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/thor-1.2.1/lib/thor/base.rb:485:in `start'
        from /opt/mastodon/bin/tootctl:9:in `block in <main>'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/chewy-7.2.4/lib/chewy/strategy.rb:57:in `wrap'
        from /opt/mastodon/vendor/bundle/ruby/3.0.0/gems/chewy-7.2.4/lib/chewy.rb:154:in `strategy'
        from /opt/mastodon/bin/tootctl:8:in `<main>'

Enter the website container shell in that instance.

docker exec -it mastodon-website-1 /bin/bash

execute the upcoming command.

sed -E '/progress.total = /d' -i lib/mastodon/search_cli.rb

Then Exit the container shell,

exit

Additional Helper Services

Let's build another service for deleting media files that have been downloaded,

nano /etc/systemd/system/mastodon-media-remove.service

Add the following lines into /etc/systemd/system/mastodon-media-remove.service file and save it.

[Unit]
Description=Mastodon - media remove service
Wants=mastodon-media-remove.timer

[Service]
Type=oneshot
StandardError=null
StandardOutput=null

WorkingDirectory=/opt/mastodon
ExecStart=/usr/bin/docker compose -f /opt/mastodon/docker-compose.yml run --rm shell tootctl media remove

[Install]
WantedBy=multi-user.target

You can set up a timer service for the media removal if you wish to arrange it,

nano /etc/systemd/system/mastodon-media-remove.timer

Add the Following Lines into /etc/systemd/system/mastodon-media-remove.timer file and save it.

[Unit]
Description=Mastodon - preview cards remove service
Wants=mastodon-preview_cards-remove.timer

[Service]
Type=oneshot
StandardError=null
StandardOutput=null

WorkingDirectory=/opt/mastodon
ExecStart=/usr/bin/docker compose -f /opt/mastodon/docker-compose.yml run --rm shell tootctl preview_cards remove

[Install]
WantedBy=multi-user.target

The Rich preview cards created with OpenGraph tags can be deleted using a different service that you can set up,

nano /etc/systemd/system/mastodon-preview_cards-remove.service

Add the following lines into it /etc/systemd/system/mastodon-preview_cards-remove.service file and save it.

[Unit]
Description=Mastodon - preview cards remove service
Wants=mastodon-preview_cards-remove.timer

[Service]
Type=oneshot
StandardError=null
StandardOutput=null

WorkingDirectory=/opt/mastodon
ExecStart=/usr/bin/docker compose -f /opt/mastodon/docker-compose.yml run --rm shell tootctl preview_cards remove

[Install]
WantedBy=multi-user.target

Select the appropriate timer service.

nano /etc/systemd/system/mastodon-preview_cards-remove.timer

Add the following lines into it /etc/systemd/system/mastodon-preview_cards-remove.timer and save it.

[Unit]
Description=Schedule a preview cards remove every week

[Timer]
Persistent=true
OnCalendar=Sat *-*-* 00:00:00
Unit=mastodon-preview_cards-remove.service

[Install]
WantedBy=timers.target

Reload the system daemon.

systemctl daemon-reload

Enable and start the timers.

systemctl enable --now mastodon-preview_cards-remove.timer
systemctl enable --now mastodon-media-remove.timer

List all the timers to check the schedule of the Mastodon services.

systemctl list-timers
.....
Sat 2023-01-07 00:00:00 UTC 6 days left    n/a                         n/a                mastodon-media-remove.timer         mastodon-media-remove.service
Sat 2023-01-07 00:00:00 UTC 6 days left    n/a                         n/a                mastodon-preview_cards-remove.timer mastodon-preview_cards-remove.service

Now, Access the Mastdon site on your browser https://domain.example.com/

As you can see in the screenshot above, there are two users, one is set to be the administrator. Typically, this is not the case. Even after creating an administrator account, it does not initially appear on the home page. You can do that by logging into your instance, which will then direct you to the next page.

To access the settings, select Preferences from the right sidebar. Access Mastodon's administration panel from there, select the Administration option from the left menu.

From the left sidebar, select the Site settings option.

Here, provide your contact username and professional email, which will now appear on the home page of your server. To further personalise your Mastodon instance, add many more fields such as the server description, logo, and rules.

This concludes the installation of Mastodon on a Ubuntu 22.04 Server.