<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Павел</title><generator>teletype.in</generator><description><![CDATA[Однажды наткнулся на open-source self-hosted софт и пропал в докерах, контейнерах и линуксах]]></description><image><url>https://img2.teletype.in/files/53/74/53746b5e-ae51-416a-9405-dc2ec37ddeca.png</url><title>Павел</title><link>https://teletype.in/@nims</link></image><link>https://teletype.in/@nims?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nims</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/nims?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/nims?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Mon, 14 Sep 2026 05:31:20 GMT</pubDate><lastBuildDate>Mon, 14 Sep 2026 05:31:20 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@nims/P9at5enfyUa</guid><link>https://teletype.in/@nims/P9at5enfyUa?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nims</link><comments>https://teletype.in/@nims/P9at5enfyUa?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nims#comments</comments><dc:creator>nims</dc:creator><title>Mastodon + Docker + Portainer + Nginx. Открываем двери федивёрса на VPS Ubuntu 22.04</title><pubDate>Thu, 08 Feb 2024 08:42:03 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/39/34/39342aed-9d11-4f84-b991-1d4cfd2c784a.png"></media:content><category>SelfHost</category><tt:hashtag>open_source</tt:hashtag><tt:hashtag>docker</tt:hashtag><tt:hashtag>ubuntu</tt:hashtag><tt:hashtag>mastodon</tt:hashtag><tt:hashtag>linux</tt:hashtag><tt:hashtag>portainer</tt:hashtag><tt:hashtag>self_host</tt:hashtag><tt:hashtag>nginx</tt:hashtag><tt:hashtag>fediverse</tt:hashtag><description><![CDATA[<img src="https://img1.teletype.in/files/45/f5/45f5f71a-d63d-4c0c-b92b-457d0fdab295.jpeg"></img>Mastodon — социальная сеть микроблогов, использующая в своей основе модель децентрализации, называемую также федерацией, федиверсом или федеративной вселенной реализованную с помощью протокола ActivityPub. Он имеет открытый исходный код и может быть развернут любым пользователем на любом сервере, причем каждый сервер является полностью самостоятельным экземпляром Mastodon. Каждый экземпляр может иметь свои собственные политики, правила модерации и управляется только Вами.]]></description><content:encoded><![CDATA[
  <figure id="UrM8" class="m_column" data-caption-align="center">
    <img src="https://img1.teletype.in/files/45/f5/45f5f71a-d63d-4c0c-b92b-457d0fdab295.jpeg" width="1600" />
    <figcaption>Mastodon - идеальный сервис для знакомства с федивёрс!</figcaption>
  </figure>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <blockquote id="wFFD">Mastodon — социальная сеть микроблогов, использующая в своей основе модель децентрализации, называемую также федерацией, федиверсом или федеративной вселенной реализованную с помощью протокола ActivityPub. Он имеет открытый исходный код и может быть развернут любым пользователем на любом сервере, причем каждый сервер является полностью самостоятельным экземпляром Mastodon. Каждый экземпляр может иметь свои собственные политики, правила модерации и управляется только Вами. В федерации нет корпоративных модераторов от компаний-разработчиков, управление всем контентом осуществляется только Вами и никто извне не сможет вас ограничить в рамках вашего экземпляра. В федерации, состоящей из всех пользовательских серверов Mastodon (и других сервисов, например Pixelfed, PeerTube и т. д.), каждый экземпляр может быть осведомлен о других экземплярах по протоколу ActivityPub. Это позволяет взаимодействовать с другими пользователями как внутри одного сервера, так и между разными экземплярами Mastodon.</blockquote>
  </section>
  <section style="background-color:hsl(hsl(199, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <blockquote id="t0U1">Это руководство - перенос моей существующей статьи из self-host инстанса Ghost. На сегодняшний день последняя актуальная версия Mastodon 4.2.5. Однако, там фундаментально ничего не изменилось с 4.2.1 и все будет работать так, как описано в этом руководстве. В ближайшее время я постараюсь обновить это руководство, т.к. если с актуальностью технической части все хорошо, то некоторые логические последовательности стоит переписать для простоты понимания.</blockquote>
  </section>
  <p id="rdLH">В этом руководстве вы установите последнюю версию Mastodon, которая на момент написания статьи является <a href="https://github.com/mastodon/mastodon/releases/tag/v4.2.1?ref=nano.sudoers.pro" target="_blank">4.2.1</a>. Настроите свой сервер Ubuntu 22.04, установите <a href="https://www.docker.com/?ref=nano.sudoers.pro" target="_blank">Docker</a> для работы с контейнерами, а так же настроите <a href="https://www.portainer.io/?ref=nano.sudoers.pro" target="_blank">Portainer</a> для управление Вашими Docker-контейнерами через графический интерфейс, а не командную строку. Также установите proxy-сервер nginx и получите для всех своих сервисов SSL/TLS Let&#x27;s Encrypt сертификаты для защищенного HTTPS соединения.</p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <blockquote id="LNcF">Данное руководство можно считать расширенной версией большинства руководств. Я не буду рассказывать «как запустить инстанс за 2 минуты», чтобы потом удалить его через 20 минут, т. к. он совершенно не подходит для дальнейшего развития. Я расскажу как сделать правильно и визуально приятно глазу, чтобы вы сами видели результат своих трудов и могли смотреть логи и ошибки в графическом интерфейсе, а не в черном окошке. Поэтому тут будут моменты, когда было бы проще сделать «все в одной корзине» и Вы бы просто ноубрейн вставляли код, но я буду объяснять почему так и зачем. Чтобы в дальнейшем вы понимали что Вы делали и зачем это делали. Мы будем идти немного более сложным путем, чем могли бы, но это Вам пригодится потом, когда Вы, например, захотите вынести свою базу данных на отдельный VPS сервер и держать ее там для повышения производительности и безопасности. Опыт из данного руководства подготовит Вас к этому. Так что старт будет немного тернист, но перспективы прекрасны.</blockquote>
  </section>
  <p id="UOMy">Что у Вас уже есть перед началом:</p>
  <ul id="roG1">
    <li id="DAgr">Арендованный у cloud-провайдера VPS сервер Ubuntu 22.04 (рекомендую Timeweb);</li>
    <li id="xoe5">Ваш домен куплен и его DNS А-записи указывают на IPv4 адрес вашего VPS сервера (например <code>example.com</code>).</li>
    <li id="0eaT">Создан поддомен для Portainer и А-записи поддомена также указывают на IPv4 адрес вашего VPS сервера (например <code>panel.example.com</code>).</li>
  </ul>
  <p id="3AGo">Ну что же, начинаем!</p>
  <h2 id="%D0%BF%D0%BE%D0%B4%D0%B3%D0%BE%D1%82%D0%BE%D0%B2%D0%BA%D0%B0">Подготовка</h2>
  <hr />
  <p id="n24q">Для начала установим весь необходимый софт для установки и дальнейшего обслуживания нашей ноды Mastodon:</p>
  <pre id="j3WU">sudo apt install wget curl nano software-properties-common dirmngr apt-transport-https gnupg gnupg2 ca-certificates lsb-release ubuntu-keyring unzip ufw htop -y</pre>
  <p id="HP9n">Зададим политики файрвола, чтобы контролировать порты на Вашем сервере:</p>
  <pre id="2uL4">sudo ufw allow OpenSSH
sudo ufw allow http
sudo ufw allow https</pre>
  <p id="Yze6">И включим файрвол:</p>
  <pre id="WN4d">sudo ufw enable</pre>
  <p id="X8qB">Это все, что необходимо сделать с сервером для стабильной работы. Дальше устанавливаем Docker, который в дальнейшем будет отвечать за контейнеры Mastodon.</p>
  <p id="jJo4">Сначала убедимся, что у Вас нет никаких старых установок Docker и от него нет никакого мусора в системе, а если есть, то удалим это:</p>
  <pre id="bKvJ">for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done</pre>
  <p id="IDnF">Обновляем систему:</p>
  <pre id="ppof">sudo apt-get update</pre>
  <p id="ZCz9">Добавляем APT репозиторий Docker на сервер:</p>
  <pre id="bFzj">sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg</pre>
  <pre id="kQKC">echo \
  &quot;deb [arch=&quot;$(dpkg --print-architecture)&quot; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  &quot;$(. /etc/os-release &amp;&amp; echo &quot;$VERSION_CODENAME&quot;)&quot; stable&quot; | \
  sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null
sudo apt-get update</pre>
  <p id="xKmF">Теперь устанавливаем сам Docker:</p>
  <pre id="LmZ6">sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</pre>
  <p id="B1i1">Удостоверимся, что Docker работает правильно:</p>
  <pre id="AGB7">sudo docker run hello-world</pre>
  <p id="0Pwa">Чтобы два раза не возвращаться к Docker, сразу установим к нему Portainer. Это делается в пару команд.</p>
  <p id="r89j">Для начала создадим docker-том, где будет работать Portainer:</p>
  <pre id="8wks">docker volume create portainer_data</pre>
  <p id="jBdO">И установим сам Portainer:</p>
  <pre id="pjvq">docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest</pre>
  <p id="H653">Все. Теперь, если Вы перейдете по вашему IP адресу сервера (например х.х.х.х:9443) или по адресу домена, который вы привязали к вашему серверу (например panel.example.com:9443), то вы попадаете в графический интерфейс панели управления Portainer, который в свою очередь управляет Docker установленный на вашем сервере. В дальнейшем Вы избавитесь от необходимости указывать порт 9443 для доступа к Portainer.</p>
  <h2 id="%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-mastodon">Установка Mastodon</h2>
  <hr />
  <p id="VosK">Для начала Вам необходимо создать директории на нашем сервере, где Вы будете хранить ваши данные. Я предпочитаю все разделять по директориям, чтобы потом понимать где что искать.</p>
  <p id="oFQA">Начнем с создания директорий для базы данных:</p>
  <pre id="Magd">sudo mkdir -p /opt/database/mastodon/{postgresql,pgbackups,redis}</pre>
  <p id="HB5n">и сразу задаем права для директории:</p>
  <pre id="BP9N">sudo chown 70:70 /opt/database/mastodon/pgbackups</pre>
  <p id="U6Dz">Точно так же создаем директорию для данных самого Mastodon:</p>
  <pre id="CNzP">sudo mkdir -p /opt/mastodon/web/{public,system}
sudo mkdir -p /opt/mastodon/branding</pre>
  <p id="fGOu">И задаем права для них:</p>
  <pre id="neUa">sudo chown 991:991 /opt/mastodon/web/{public,system}</pre>
  <p id="UCC5">После этого идем в установленный ранее Portainer. При первом запуске он предложит вам зарегистрировать аккаунт администратора.</p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <blockquote id="8e0I">примечание:<br />при первом заходе в Portainer, возможно он Вас попросит перезагрузить свой контейнер и не даст создать аккаунт. В этом случае просто перезагрузите контейнер командой <code>docker restart portainer_agent</code> и зайдите в Portainer снова.</blockquote>
  </section>
  <p id="ZQmC">После входа в Portainer перейдите на вкладку <code>Home</code> и нажмите <code>Live Connect</code> к вашему единственному серверу. Дальше в левой части экрана у вас появится новое меню, где Вас интересует пункт <code>Stack</code>. Переходите в него и нажимайте <code>+add stack</code>.</p>
  <p id="Ix77">Перед Вами откроется окно для создания файла docker-compose.yml, выглядит это так:</p>
  <figure id="zVeA" class="m_column" data-caption-align="center">
    <img src="https://img3.teletype.in/files/ee/c9/eec9c92d-1d5f-4d91-ab6e-265d33368938.png" width="1244" />
    <figcaption>docker-compose.yml в Portainer</figcaption>
  </figure>
  <p id="Ci5h">Здесь вводите название на Ваш выбор. Главное, чтобы Вы сами понимали что оно означает. Например <code>mastodon_db</code>, а в основное окно ниже вставляем следующий код:</p>
  <pre id="RKOM">version: &#x27;3&#x27;
services:

  postgres:
    image: postgres:15-alpine
    container_name: postgres
    shm_size: 512mb
    restart: always
    network_mode: bridge
    ports:
      - &#x27;5432:5432&#x27;
    healthcheck:
      test: [&#x27;CMD&#x27;, &#x27;pg_isready&#x27;, &#x27;-U&#x27;, &#x27;postgres&#x27;]
    volumes:
      - postgresql:/var/lib/postgresql/data
      - pgbackups:/backups
    environment:
      - &#x27;POSTGRES_HOST_AUTH_METHOD=trust&#x27;

  redis:
    image: redis:7-alpine
    container_name: redis
    restart: always
    network_mode: bridge
    ports:
      - &#x27;6379:6379&#x27;
    healthcheck:
      test: [&#x27;CMD&#x27;, &#x27;redis-cli&#x27;, &#x27;ping&#x27;]
    volumes:
      - redis:/data

  redis-volatile:
    image: redis:7-alpine
    container_name: redis-volatile
    restart: always
    network_mode: bridge
    healthcheck:
      test: [&#x27;CMD&#x27;, &#x27;redis-cli&#x27;, &#x27;ping&#x27;]

volumes:
  postgresql:
    driver_opts:
      type: none
      device: /opt/database/mastodon/postgresql
      o: bind
  pgbackups:
    driver_opts:
      type: none
      device: /opt/database/mastodon/pgbackups
      o: bind
  redis:
    driver_opts:
      type: none
      device: /opt/database/mastodon/redis
      o: bind</pre>
  <p id="aycf">После этого внизу экрана нажимаем <code>Deploy the stack</code> и ждем, когда появится уведомление о удачном развертываний Ваших баз данных.</p>
  <p id="H4Y4">Теперь точно так же делаем новый <code>Stack</code>, называем его как-то удобно для Вас, например <code>mastodon_web</code> и копируем следующий код:</p>
  <pre id="sLTL">version: &#x27;3&#x27;
services:

  website:
    image: ghcr.io/mastodon/mastodon:v4.2.1
    container_name: website
    env_file: stack.env
    command: bash -c &quot;rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000&quot;
    restart: always
    ports:
      - &#x27;127.0.0.1:3000:3000&#x27;
    networks:
      - internal_network
      - external_network
    healthcheck:
      test: [&#x27;CMD-SHELL&#x27;, &#x27;wget -q --spider --proxy=off localhost:3000/health || exit 1&#x27;]
    volumes:
      - uploads:/mastodon/public/system

  streaming:
    image: ghcr.io/mastodon/mastodon:v4.2.1
    container_name: streaming
    env_file: stack.env
    command: node ./streaming
    restart: always
    ports:
      - &#x27;127.0.0.1:4000:4000&#x27;
    networks:
      - internal_network
      - external_network
    healthcheck:
      test: [&#x27;CMD-SHELL&#x27;, &#x27;wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1&#x27;]

  sidekiq:
    image: ghcr.io/mastodon/mastodon:v4.2.1
    container_name: sidekiq
    env_file: stack.env
    command: bundle exec sidekiq
    restart: always
    depends_on:
      - website
    networks:
      - internal_network
      - external_network
    healthcheck:
      test: [&#x27;CMD-SHELL&#x27;, &quot;ps aux | grep &#x27;[s]idekiq\ 6&#x27; || false&quot;]
    volumes:
      - uploads:/mastodon/public/system

  shell:
    image: ghcr.io/mastodon/mastodon:v4.2.1
    container_name: shell
    env_file: stack.env
    command: /bin/bash
    restart: &quot;no&quot;
    networks:
      - internal_network
      - external_network
    volumes:
      - uploads:/mastodon/public/system
      - static:/static

networks:
  external_network:
  internal_network:
    internal: true

volumes:
  uploads:
    driver_opts:
      type: none
      device: /opt/mastodon/web/system
      o: bind
  static:
    driver_opts:
      type: none
      device: /opt/mastodon/web/public
      o: bind</pre>
  <p id="TlOM">Ниже этого окна будет еще один пункт, где вам нужно нажать <code>advanced mode</code>:</p>
  <figure id="FQ3C" class="m_column">
    <img src="https://img4.teletype.in/files/f8/04/f804f9de-67e4-48a1-9b19-b9f6d430da7a.png" width="914" />
    <figcaption>Advanced mode для заполнения переменных в docker-compose</figcaption>
  </figure>
  <p id="Q79C">В открывшееся окно вставляем значение переменных окружения (.env), которыми будет пользоваться Ваш Mastodon:</p>
  <pre id="VLEs">LOCAL_DOMAIN=example.com
SINGLE_USER_MODE=false
SECRET_KEY_BASE=257935a5e78673fc79b8888888888bb8a9e58452aa8aab6896794b0e31d8b77cf8888888888a96818913f3213472bb857e3f888888888805da1daeb1e88b9b53
OTP_SECRET=03d99fe6cd8dd316bb962ec366888888888822fcc0b4a31795554b5cee1f5d685fd88888888885c96979ca4f8097851cf5ca898888888888dfcdaa6ddce2fae1
VAPID_PRIVATE_KEY=8hk8888888888RpDA8888888888ZWHm9KYKIpIrhMj0=
VAPID_PUBLIC_KEY=BPZJW5ieC8888888888Zo9uyp05qUn1tbi8888888888Dxa81dvabtLCxgLUg94fliD4UJyY8888888888885lFdE=
DB_HOST=postgres
DB_PORT=5432
DB_NAME=postgres
DB_USER=mastodon
DB_PASS=verysecretpass
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
SMTP_SERVER=smtp.timeweb.ru
SMTP_PORT=25
SMTP_LOGIN=admin@example.com
SMTP_PASSWORD=0088664422
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS=Mastodon &lt;admin@example.com&gt;</pre>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <blockquote id="02RZ">примечание:<br />Тут присутствует настройка отправки почты через SMTP. Настоятельно рекомендую это сделать. Получите почтовый ящик к своему домену там же, где вы купили домен. В примере выше указаны настройки для Timeweb, в данном случаем Вам нужно будет изменить только <code>SMTP_LOGIN</code> и <code>SMTP_PASSWORD</code> на свои и все. Так же нужно заменить <code>SMTP_FROM_ADDRESS</code> на что-то свое. <code>SMTP_FROM_ADDRESS</code> задает какой адрес электронной почты отправителя будут видеть Ваши пользователи, когда им будут приходить письма от Вас с уведомлениями или регистрацией.</blockquote>
  </section>
  <p id="gvTq">После этого нажимаем <code>Deploy the stack</code> и ждем, когда все контейнеры загрузятся. Это может занять несколько минут.</p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <blockquote id="pC9v">примечание:<br />при попытке первого деплоя этого стака, возможно, спустя пару минут у Вас появится ошибка в верхнем правом углу о неудаче. Ничего страшного, можно сказать что так и должно быть. Нажимаем еще раз <code>Deploy the stack</code> и он запустится без ошибок, если вы все сделали до этого правильно.</blockquote>
  </section>
  <p id="GoPn">На данном этапе у Вас есть 2 стака, в первом находится база данных, во втором сам Mastodon. Сейчас нужно будет объединить их в одну сеть, чтобы они друг друга видели и могла общаться между собой. Для этого во втором стаке с Mastodon поочередно заходите (просто нажимайте на название) в каждый из контейнеров <code>website</code>, <code>streaming</code> и <code>sidekiq</code> и в самом низу страницы добавляйте их в сеть базы данных выбрав нужную сеть нажатием на <code>Select a network</code>. Если вы стак с базой данных назвали как я предлагал выше, то нужная сеть будет называться <code>mastodon_db_default</code>. Каждый из контейнеров <code>website</code>, <code>streaming</code> и <code>sidekiq</code> должен быть сразу в двух сетях - в своей родной и сети от базы данных, в данном случае в <code>mastodon_db_default</code>.</p>
  <h2 id="%D0%BA%D0%BE%D0%BD%D1%84%D0%B8%D0%B3%D1%83%D1%80%D0%B0%D1%86%D0%B8%D1%8F-%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D0%BE%D0%B2">Конфигурация контейнеров</h2>
  <hr />
  <p id="VakV">В целом мы сделали уже самое основное и по большому счету нам осталось это между собой подружить и запустить в рабочем состоянии, т.к. сейчас это запущено, но ничего не работает. И начинаем с конфигурации базы данных:</p>
  <pre id="zLXN">sudo docker run --name postgres14 -v /opt/database/mastodon/postgresql:/var/lib/postgresql/data -e POSTGRES_PASSWORD=verysecretpass --rm -d postgres:14-alpine</pre>
  <p id="gxlC">Заходим в контейнер с базой данных:</p>
  <pre id="POro">sudo docker exec -it postgres14 psql -U postgres</pre>
  <p id="Y70P">и создает там пользователя:</p>
  <pre id="48Tq">CREATE USER mastodon WITH PASSWORD &#x27;verysecretpass&#x27; CREATEDB;</pre>
  <p id="qLoF">Выходим из базы данных обратно:</p>
  <pre id="yrCK">exit</pre>
  <p id="60GS">и останавливаем контейнер:</p>
  <pre id="WNMZ">sudo docker stop postgres14</pre>
  <p id="fyDA">На этом наша база данных настроена и готова в работе. Осталось закончить с конфигурацией контейнера Mastodon, но тут совсем просто, т.к. он будет просить Вас ввести те значения переменных (.env), которые вы указывали выше во время запуска второго стака. Например первое, что он попросит Вас указать - это адрес вашего домена <code>example.com</code>. В самом конце Вам будет предложено проверить правильность настройки SMTP и попросит ввести e-mail адрес куда отправить тестовое письмо. Сделайте это, чтобы быть уверенным, что все работает исправно. Когда будут введены все переменные из таблицы выше (то, что вы указывали в <code>advanced mode</code> в Portainer), соглашайтесь на предпоследний вопрос о подготовке базы данных и он Вам выдаст новый список переменных (для поля <code>advanced mode</code> в Portainer). Дальше будет предложено создать аккаунт администратора, от этого откажитесь.</p>
  <p id="f1tn">Начинаем конфигурацию Mastodon:</p>
  <pre id="Q6Ur">sudo docker exec -it streaming bundle exec rake mastodon:setup</pre>
  <p id="i8pm">Скопируйте список переменных из терминала, вернитесь в Portainer во второй стак <code>mastodon_web</code> и вставьте теперь уже правильные переменные в <code>advanced mode</code>. После этого нажмите внизу страницы <code>update the stack</code>.</p>
  <p id="96yR">После того, как контейнеры будут перезагружены, убедитесь, что все 3 контейнера <code>website</code>, <code>streaming</code> и <code>sidekiq</code> находятся в сети <code>mastodon_db_default</code>, как мы это делали ранее заходя в каждый из них по очереди, если нет, то добавьте их снова в сеть.</p>
  <p id="0cle">Теперь нам осталось только создать администратора и на этом конфигурация самого Mastodon будет окончена. Для этого используйте пару следующих команд.</p>
  <p id="d4Ca">Заходим в контейнер Mastodon:</p>
  <pre id="mBBC">sudo docker exec -it streaming /bin/bash</pre>
  <p id="KY4p">Создаем аккаунт администратора:</p>
  <pre id="Igtv">RAILS_ENV=production bin/tootctl accounts create admin --email admin@example.com --role=Owner</pre>
  <p id="cYp4">Можете заменить <code>admin</code> на свой ник и почту тоже заменить на свою.</p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <blockquote id="ENFh">примечание:<br />Обратите внимание, что после выполнения команды выше, в окне терминала будет указан пароль от этого аккаунта. Запишите его сразу куда-то, чтобы не потерять.</blockquote>
  </section>
  <p id="vLlQ">Сразу подтверждаем свой аккаунт, без необходимости делать это через почту:</p>
  <pre id="x8cn">RAILS_ENV=production bin/tootctl accounts modify admin --confirm</pre>
  <p id="c6Ze">Замените <code>admin</code> на Ваш ник, если вы его заменили в прошлой команде.</p>
  <p id="uzN6">Выходим из контейнера:</p>
  <pre id="4sG3">exit</pre>
  <p id="HByc">На этом экземпляр Mastodon готов и полностью готов к работе. Осталось дело за малым - выпустить SSL сертификаты и настроить nginx.</p>
  <h2 id="%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-nginx">Установка NGINX</h2>
  <hr />
  <p id="93Z0">Импортируем ключи nginx:</p>
  <pre id="62f8">curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
	| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg &gt;/dev/null</pre>
  <p id="j3Y4">Добавляем репозиторий стабильной версии nginx:</p>
  <pre id="8Pge">echo &quot;deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg arch=amd64] \
http://nginx.org/packages/ubuntu &#x60;lsb_release -cs&#x60; nginx&quot; \
    | sudo tee /etc/apt/sources.list.d/nginx.list</pre>
  <p id="zMwN">Обновляем систему:</p>
  <pre id="otvR">sudo apt update</pre>
  <p id="7rQP">И устанавливаем nginx:</p>
  <pre id="tLbg">sudo apt install nginx</pre>
  <p id="H5uC">Запускаем сервис nginx:</p>
  <pre id="7Caf">sudo systemctl start nginx</pre>
  <h3 id="%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B0%D0%B5%D0%BC-ssl-%D1%81%D0%B5%D1%80%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D1%8B">Получаем SSL-сертификаты</h3>
  <hr />
  <p id="PHOo">Для получения сертификатов нам нужен Certbot. Есть разные версии, но мы будем использовать версию последнюю версию в Snapd:</p>
  <pre id="o9qL">sudo snap install core</pre>
  <pre id="QJHI">sudo snap refresh core</pre>
  <p id="EItX">Устанавливаем Certbot:</p>
  <pre id="dHB0">sudo snap install --classic certbot</pre>
  <p id="XjMK">Для исправной работы Certbot создаем символическую ссылку:</p>
  <pre id="8U0I">sudo ln -s /snap/bin/certbot /usr/bin/certbot</pre>
  <p id="W2hq">Если вы правильно настроили свои DNS A-записи для своего домена, то следующая команда сгенерирует Вам SSL сертификаты:</p>
  <pre id="OjVi">sudo certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m name@example.com -d example.com</pre>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <blockquote id="nNkN">Замените почту и адрес домена <code>example.com</code> на свои.</blockquote>
  </section>
  <p id="8u5I">Так же сразу получите сертификаты для своего поддомена для Portainer, заменив <code>example.com</code> на <code>panel.example.com</code>.</p>
  <p id="dmwy">Получаем сертификат Diffie-Hellman:</p>
  <pre id="Plzr">sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096</pre>
  <p id="90qX">Убедитесь, что обновление сертификатов работает исправно, выполнив следующую команду:</p>
  <pre id="o1LI">sudo certbot renew --dry-run</pre>
  <h3 id="%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%B0%D0%B8%D0%B2%D0%B0%D0%B5%D0%BC-nginx">Настраиваем NGINX</h3>
  <hr />
  <p id="xd7A">Открываем файлик конфига с помощью редактора <code>nano</code>:</p>
  <pre id="FEVu">sudo nano /etc/nginx/nginx.conf</pre>
  <p id="q5HE">В открывшемся файле найдите строку <code>include /etc/nginx/conf.d/*.conf;</code> и <code>перед этой</code> строкой вставьте следующую строку:</p>
  <pre id="xjM2">server_names_hash_bucket_size 64;</pre>
  <p id="KJDO">Нажмите <code>Ctrl + S</code>, чтобы сохранить изменения и закройте редактор нажатием <code>Ctrl + X</code>.</p>
  <p id="xFLU">Создайте и откройте в редакторе файл конфигурации Nginx:</p>
  <pre id="Dirk">sudo nano /etc/nginx/conf.d/mastodon.conf</pre>
  <p id="QHV2">и вставьте туда следующий код:</p>
  <pre id="0XpS">map $http_upgrade $connection_upgrade {
  default upgrade;
  &#x27;&#x27;      close;
}

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

upstream streaming {
    # Instruct nginx to send connections to the server with the least number of connections
    # to ensure load is distributed evenly.
    least_conn;

    server 127.0.0.1:4000 fail_timeout=0;
    # Uncomment these lines for load-balancing multiple instances of streaming for scaling,
    # this assumes your running the streaming server on ports 4000, 4001, and 4002:
    # server 127.0.0.1:4001 fail_timeout=0;
    # server 127.0.0.1:4002 fail_timeout=0;
}

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

server {
  listen 80;
  listen [::]:80;
  server_name example.com;
  root /opt/mastodon/web/public;
  location /.well-known/acme-challenge/ { allow all; }
  location / { return 301 https://$host$request_uri; }
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name example.com;

  ssl_protocols TLSv1.2 TLSv1.3;

  # You can use https://ssl-config.mozilla.org/ to generate your cipher set.
  # We recommend their &quot;Intermediate&quot; level.
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;

  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;
  ssl_session_tickets off;

  # Uncomment these lines once you acquire a certificate:
   ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 99m;

  root /opt/mastodon/web/public;

  gzip on;
  gzip_disable &quot;msie6&quot;;
  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;

  location / {
    try_files $uri @proxy;
  }

  # If Docker is used for deployment and Rails serves static files,
  # then needed must replace line &#x60;try_files $uri =404;&#x60; with &#x60;try_files $uri @proxy;&#x60;.
  location = /sw.js {
    add_header Cache-Control &quot;public, max-age=604800, must-revalidate&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    try_files $uri @proxy;
  }

  location ~ ^/assets/ {
    add_header Cache-Control &quot;public, max-age=2419200, must-revalidate&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    try_files $uri @proxy;
  }

  location ~ ^/avatars/ {
    add_header Cache-Control &quot;public, max-age=2419200, must-revalidate&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    try_files $uri @proxy;
  }

  location ~ ^/emoji/ {
    add_header Cache-Control &quot;public, max-age=2419200, must-revalidate&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    try_files $uri @proxy;
  }

  location ~ ^/headers/ {
    add_header Cache-Control &quot;public, max-age=2419200, must-revalidate&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    try_files $uri @proxy;
  }

  location ~ ^/packs/ {
    add_header Cache-Control &quot;public, max-age=2419200, must-revalidate&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    try_files $uri @proxy;
  }

  location ~ ^/shortcuts/ {
    add_header Cache-Control &quot;public, max-age=2419200, must-revalidate&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    try_files $uri @proxy;
  }

  location ~ ^/sounds/ {
    add_header Cache-Control &quot;public, max-age=2419200, must-revalidate&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    try_files $uri @proxy;
  }

  location ~ ^/system/ {
    add_header Cache-Control &quot;public, max-age=2419200, immutable&quot;;
    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;
    add_header X-Content-Type-Options nosniff;
    add_header Content-Security-Policy &quot;default-src &#x27;none&#x27;; form-action &#x27;none&#x27;&quot;;
    try_files $uri @proxy;
  }

  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 &quot;&quot;;

    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;

    add_header Strict-Transport-Security &quot;max-age=63072000; includeSubDomains&quot;;

    tcp_nodelay on;
  }

  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 &quot;&quot;;
    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;

    tcp_nodelay on;
  }

  error_page 404 500 501 502 503 504 /500.html;
}</pre>
  <p id="YVLH">В этом коде есть 4 места, где нужно заменить <code>example.com</code> на ваш адрес домена Mastodon.</p>
  <p id="APjT">И сразу создайте файл конфига Nginx для Portainer:</p>
  <pre id="rXz7">sudo nano /etc/nginx/conf.d/portainer.conf</pre>
  <p id="FnYR">И вставьте туда следующий код:</p>
  <pre id="y3gk">upstream portainer {
    server 0.0.0.0:9443;
}

server {
    listen 80;
    server_name panel.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;

    # The block below works only for the selected domains
    server_name panel.example.com;

    ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem; # managed by Certbot
    ssl_trusted_certificate /etc/letsencrypt/live/panel.example.com/chain.pem;

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

    location / {
        proxy_pass https://portainer/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &quot;Upgrade&quot;;
        proxy_set_header Host $host;
    }

    location /api/websocket/ {
        proxy_pass https://portainer/api/websocket/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &quot;Upgrade&quot;;
        proxy_set_header Host $host;
    }
}</pre>
  <p id="C6uz">В этом коде так же есть 4 места, где нужно заменить <code>panel.example.com</code> на ваш адрес поддомена Portainer.</p>
  <p id="sCYt">Теперь перезапустим Nginx командой:</p>
  <pre id="9bDi">sudo systemctl restart nginx</pre>
  <p id="7Asb">Если все работает исправно, что никакого вывода в терминале не будет. Вы перезапустите Nginx и все.</p>
  <p id="PR9n">Можете проверить синтаксис файлов конфигов Nginx следующей командой:</p>
  <pre id="oFSr">sudo nginx -t</pre>
  <p id="Jm5M">Теперь, если вы перейдете по адресу своего домена <code>example.com</code>, то у вас будет открываться Ваш экземпляр Mastodon.</p>
  <p id="FW8e">А переход по адресу <code>panel.example.com</code> будет открывать Ваш Portainer.</p>
  <p id="fH1N">На этом все. У вас есть возможность отслеживать логи своего инстанса Mastodon, его поведение, обновление, поддержка и много другой работы через графический интерфейс на вашем домене Portainer, без необходимости постоянно открывать терминал и подключаться к VPS по SSH. Теперь дело за Вами, как Вы будете это развивать и администрировать. Удачи Вам на просторах федиверса!</p>
  <figure id="AtpO" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/7c/a5/7ca55043-f6fe-4dd4-b70f-ba553c913ac4.png" width="1000" />
    <figcaption>Mastodon во всей красе!</figcaption>
  </figure>
  <h2 id="%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D0%B5">Послесловие</h2>
  <hr />
  <p id="cJxy">Этим руководством я решил открыть свой маленький блог, где я планирую писать различные руководства для open-source софта, self-host ПО, Linux и т.д.</p>
  <hr />
  <tt-tags id="kWaC">
    <tt-tag name="open_source">#open_source</tt-tag>
    <tt-tag name="docker">#docker</tt-tag>
    <tt-tag name="ubuntu">#ubuntu</tt-tag>
    <tt-tag name="mastodon">#mastodon</tt-tag>
    <tt-tag name="linux">#linux</tt-tag>
    <tt-tag name="portainer">#portainer</tt-tag>
    <tt-tag name="self_host">#self_host</tt-tag>
    <tt-tag name="nginx">#nginx</tt-tag>
    <tt-tag name="fediverse">#fediverse</tt-tag>
  </tt-tags>

]]></content:encoded></item></channel></rss>