Ubuntu16.04にDockerでMastodonインスタンスを立てる

# はじめに
Dockerの勉強がてらに、今更感がありますが、Mastodonのインスタンスを立ててみました。

リバースプロキシや、サーバ証明書の自動更新をすべてDockerで
動かしたかったので以下の記事を参考にさせていただきました。
[Docker🐳でMastodon🐘のインスタンスを立てるドン ](http://qiita.com/ryo_dg/items/e0cc93e6a8688e5116c8)
[リバースプロキシ + https + 全てコンテナ、な Mastodon インスタンス（無料）を構築してみたメモ](http://blog.a-know.me/entry/2017/05/08/081418)

構築にあたっては、基本的に、ほとんど参考記事に従っただけですが、異なる部分や、何点かハマッたポイントもありましたので、備忘録として残しておこうと思います。

# 0. 前提条件
## 0.1. 環境
* サーバ：さくらVPS
* OS：Ubuntu16.04
* Mastodon：v1.3.3

## 0.2. 構築のポイント
* Nginxによるリバースプロキシを立てる
* Let's Encrypt によるサーバ証明書自動更新を行う
* 上記を含め、すべてDockerで動かす
* メールは手抜きでGmailを使用 

## 0.3. 注意点
* 証明書を取得するのに、Aレコードに登録した独自ドメインが必要。
* Let's Encryptには証明書作成の制限がある。
　(知らずに作って潰して繰り返してたら制限掛かった)
* Mastodonは、バージョンタグを指定してcheckoutする。

# 1. 事前準備
## 1.1. ポートの開放
```
$ sudo iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
$ sudo iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
```
## 1.2. Dockerのインストール
```
$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
$ sudo apt-get install apt-transport-https \
　ca-certificates curl software-properties-common

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add
$ sudo add-apt-repository \
　"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable"

$ sudo apt-get update
$ sudo apt-get install docker-ce
$ sudo apt-get upgrade
$ sudo docker run hello-world

$ sudo groupadd docker
$ sudo usermod -aG docker $USER
$ exit
$ docker run hello-world
$ sudo systemctl enable docker
```

## 1.3. Docker-composeのインストール
```
$ sudo -i

$ curl -L https://github.com/docker/compose/releases/download/1.12.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
$ exit
```

## 1.4. Dockerネットワークを作成する
フロント(リバースプロキシ)側と
Mastodon側のDockerネットワークをそれぞれ作成します。

```
$ docker network create --driver bridge front
$ docker network create --driver bridge back-mstdn
```

# 2. リバースプロキシの設定と起動
ホームディレクトリ配下にproxyというディレクトリを作成し、
その中にまとめました。

## 2.1. docker-compose.ymlを作成する

```
#ディレクトリを作成
$ sudo mkdir -p proxy
$ cd proxy

#ファイルを新規で作成して編集
$ sudo vi docker-compose.yml
```

docker-compose.ymlの内容は以下の通り。
これはどんな環境でも変わりはなさそうです。

```~/proxy/docker-compose.yml
version: '2'
services:
  proxy:
    image: jwilder/nginx-proxy:alpine
    container_name: proxy-nginx
    ports:
      - 80:80
      - 443:443
    restart: always
    tty: false
    privileged: true
    volumes:
      - ./certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - /etc/nginx/vhost.d
      - /usr/share/nginx/html
    networks:
      - front

  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: proxy-letsencrypt
    restart: always
    tty: false
    privileged: true
    volumes:
      - ./certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
    volumes_from:
      - proxy
    networks:
      - front

networks:
  front:
    external: true

```

## 2.2. コンテナ起動

```
$ docker-compose up -d

#起動後のプロセスやログを確認する場合
以下のコマンドを実行する。
$ docker-compose ps
$ docker-compose logs -f
```

## 3. Mastdonのビルドする
他のアプリケーションを動かす場合に備えて、
ホームディレクトリ配下にappsディレクトリをして、
その中にまとめる。

## 3.1. Mastodonのクローン

```
$ sudo mkdir ~/apps
$ cd ~/apps
$ sudo git clone https://github.com/tootsuite/mastodon.git
```

## 3.2 Mastodonのビルド
注意点は、きちんとバージョンタグを指定してチェックアウトすること。
タイミングが悪いと、何やっても動かなかったりします。
以下はv1.3.3の例です。

```
$ cd mastodon

#バージョンタグの確認
$ git tag -l

#バージョン指定
$ sudo git checkout refs/tags/v1.3.3

#環境変数ファイルのコピー
$ sudo cp .env.production.sample .env.production

#ビルド実行
$ docker-compose build
```

# 4. Mastodonの設定

## 4.1. 環境変数ファイルの編集
以下のコマンドを三回実行して、それぞれ出力された値をメモしておく。

```
$ docker-compose run --rm web rake secret
```

続いて、環境変数ファイルを編集する。

```
$ sudo vi .env.production
```

ファイルの内容は以下の通り。
【】内を自身の環境に合わせて編集して下さい。

```~/apps/mastodon/.env.production
VIRTUAL_HOST=【自分のドメイン】
VIRTUAL_PORT=9090
VIRTUAL_PROTO=https
LETSENCRYPT_HOST=【自分のドメイン】
LETSENCRYPT_EMAIL=【自分のメールアドレス】
LETSENCRYPT_TEST=false

# Service dependencies
# You may set REDIS_URL instead for more advanced options
REDIS_HOST=redis
REDIS_PORT=6379
# You may set DATABASE_URL instead for more advanced options
DB_HOST=db
DB_USER=postgres
DB_NAME=postgres
DB_PASS=
DB_PORT=5432

# Federation
LOCAL_DOMAIN=【自分のドメイン】
LOCAL_HTTPS=true

# Use this only if you need to run mastodon on a different domain than the one used for federation.
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
# WEB_DOMAIN=mastodon.example.com

# Application secrets
# Generate each with the `rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
PAPERCLIP_SECRET=【3.3でコマンド実行して1回目に出力された値】
SECRET_KEY_BASE=【3.3でコマンド実行して2回目に出力された値】
OTP_SECRET=【3.3でコマンド実行して3回目に出力された値】

# Registrations
# Single user mode will disable registrations and redirect frontpage to the first profile
# SINGLE_USER_MODE=true
# Prevent registrations with following e-mail domains
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
# Only allow registrations with the following e-mail domains
# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc

# Optionally change default language
DEFAULT_LOCALE=ja

# E-mail configuration
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
# If you want to use an SMTP server without authentication (e.g local Postfix relay)
# then set SMTP_AUTH_METHOD to 'none' and *comment* SMTP_LOGIN and SMTP_PASSWORD.
# Leaving them blank is not enough for authentication method 'none'.
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_LOGIN=【自分のメールアドレス※ユーザ登録時の送信元になる】
SMTP_PASSWORD=【Gmailのパスワード】
SMTP_FROM_ADDRESS=【自分のメールアドレス※ユーザ登録時の送信元になる】
SMTP_DOMAIN=gmail.com
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
#SMTP_OPENSSL_VERIFY_MODE=peer
#SMTP_ENABLE_STARTTLS_AUTO=true


# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
# PAPERCLIP_ROOT_URL=/system

# Optional asset host for multi-server setups
# CDN_HOST=assets.example.com

# S3 (optional)
# S3_ENABLED=true
# S3_BUCKET=
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=
# S3_PROTOCOL=http
# S3_HOSTNAME=192.168.1.123:9000

# S3 (Minio Config (optional) Please check Minio instance for details)
# S3_ENABLED=true
# S3_BUCKET=
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=
# S3_PROTOCOL=https
# S3_HOSTNAME=
# S3_ENDPOINT=
# S3_SIGNATURE_VERSION=

# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
# S3_CLOUDFRONT_HOST=

# Streaming API integration
# STREAMING_API_BASE_URL=

# Advanced settings
# If you need to use pgBouncer, you need to disable prepared statements:
# PREPARED_STATEMENTS=false

# Cluster number setting for streaming API server.
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
STREAMING_CLUSTER_NUM=1

```

## 4.2. Mastodonのdocker-compose.ymlを編集

```
$ sudo vi docker-compose.yml
```

ファイルの内容は以下の通り。

```~/apps/mastodon/docker-compose.yml
version: '2'
services:
  nginx:
    image: nginx:1.11.10-alpine
    container_name: mstdn-nginx
    ports:
      - "9090:9090"
    restart: always
    tty: false
    env_file: .env.production
    links:
      - web
      - streaming
    volumes:
      - ./setting/nginx/conf.d:/etc/nginx/conf.d:ro
      - ./setting/nginx/conf:/etc/nginx/conf:ro
    volumes_from:
      - container:proxy-nginx
    networks:
      - front
      - back-mstdn

  db:
    restart: always
    image: postgres:alpine
    container_name: mstdn-db
    volumes:
      - ./postgres:/var/lib/postgresql/data
    networks:
      - back-mstdn

  redis:
    restart: always
    image: redis:alpine
    container_name: mstdn-redis
    volumes:
      - ./redis:/data
    networks:
      - back-mstdn

  web:
    restart: always
    build: .
    image: gargron/mastodon
    container_name: mstdn-web
    env_file: .env.production
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    ports:
      - "3000:3000"
    depends_on:
      - db
      - redis
    volumes:
      - ./public/assets:/mastodon/public/assets
      - ./public/system:/mastodon/public/system
    networks:
      - back-mstdn

  streaming:
    restart: always
    build: .
    image: gargron/mastodon
    container_name: mstdn-streaming
    env_file: .env.production
    command: npm run start
    ports:
      - "4000:4000"
    depends_on:
      - db
      - redis
    networks:
      - back-mstdn

  sidekiq:
    restart: always
    build: .
    image: gargron/mastodon
    container_name: mstdn-sidekick
    env_file: .env.production
    command: bundle exec sidekiq -q default -q mailers -q pull -q push
    depends_on:
      - db
      - redis
    volumes:
      - ./public/system:/mastodon/public/system
    networks:
      - back-mstdn

networks:
  front:
    external: true
  back-mstdn:
    external: true

```

## 4.3. Nginxの設定ファイルを新規作成
ディレクトリを作成して、ファイルを編集する。

```
$ sudo mkdir -p setting/nginx/conf.d
$ sudo vi setting/nginx/conf.d/default.conf
```

ファイルの内容は以下の通り。

```~/apps/mastodon/setting/nginx/conf.d/default.conf
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 9090 ssl;
  server_name 【自分のドメイン】;

  ssl_protocols TLSv1.2;
  ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;
  ssl_ecdh_curve prime256v1;

  ssl_certificate     /etc/nginx/certs/【自分のドメイン】/fullchain.pem;
  ssl_certificate_key /etc/nginx/certs/【自分のドメイン】/key.pem;
  ssl_dhparam         /etc/nginx/certs/dhparam.pem;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 0;

  root 【mastodon配下の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;

  add_header Strict-Transport-Security "max-age=31536000";
  add_header Content-Security-Policy "style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'self'; img-src data: https:; media-src data: https:; connect-src 'self' wss://【自分のドメイン】; upgrade-insecure-requests";

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(assets|system/media_attachments/files|system/accounts/avatars) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    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 https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://web:3000;
    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;
  }

  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 https;
    proxy_set_header Proxy "";

    proxy_pass http://streaming:4000;
    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;
}

```

# 5. Mastodonの起動
## 5.1 データベースのマイグレーション
```
$ docker-compose run --rm web rails db:migrate
```

## 5.2. アセットプリコンパイルの実行
```
$ docker-compose run --rm web rails assets:precompile
```

## 5.3. Mastodonの起動

```
$ docker-compose up -d

#起動後のプロセスやログを確認する場合
以下のコマンドを実行する。
$ docker-compose ps
$ docker-compose logs -f
```

# 6. 起動後

## 6.1. 起動確認
自分のドメインにアクセスして、接続出来るか確認。
サインアップの画面が表示されれば成功。

もし、「we're sorry, but something went wrong」というメッセージが出るようだったら、データベースのマイグレーションが出来ていない可能性があるので、再度コマンドを実行すると、たぶん動きます。

## 6.2. 管理者登録
アクセスした画面からサインアップを行った後、以下のコマンドを実行して
管理者に登録します。

```
$ docker-compose run --rm web rails mastodon:make_admin USERNAME=【登録ユーザー名】
```

# あとがき
Mastodonは、dockerを学ぶにはとてもよい題材だと感じました。
今回、初めてDockerを使ったのですが、それでもうっすらと概要を理解できました。