【いまさら】Docker Compose で Redmine しよう with Let's Encrypt

## やりたいこと

* [Redmine](https://redmine.jp/) を使って個人のタスク管理 (勉強、家事、趣味) などをしたい
* お金はかけたくない、イニシャルコストは 10K 程度、ランニングは月々数百円 くらいが良い

## やろうと思ったこと

### [wrike](https://www.wrike.com/ja/) も考えた

無料プランではタイムトラック機能が使えないらしい。
何に時間を使ってるかも管理したいので、残念。


###  [planio](https://plan.io/ja/)  を使おうと思った
無料プランではタグを使うためのプラグインを入れられない。有料プランは個人で使うには高すぎる。

### クラウドにホストしようかと思った

 [bitnami](https://bitnami.com/) を使ってさくっとクラウドにホストしようかととも考えたが、ランニングコストがアホほどかかる。
 bitnami の必須構成で組んでも月々数千円かかる。

## やることになったこと

### 自宅サーバを立てる

今どきではない感が半端ないが、クラウド() が自分のユースケースにはあっていないため、仕方がなく自分で運用保守することにした。
筐体は少電力省スペースの Raspberry Pi 4 4GB を購入した。どういうケースにしようとか SD カードどうしようとか考えるのが面倒なので、[日本の販売代理店](https://jp.rs-online.com/web/generalDisplay.html?id=raspberrypi)のスターターキットを買うことにした。

数年前に買ったラズパイ３はメモリ 1GB だったのに、やっぱりムーアの法則ってあるんだなぁ...。


### Docker Compose を使う

今どきならk8s とかでしょ！と思うんですが、全然、勉強をしていないので、Web に投稿されている手順や設定ファイルを読み解くことができず...。なにか変えたくなった時に手も足も出なくなりそう....。

コンテナの構成は、リバースプロキシ、AP サーバ (Redmine)、DB サーバ (MariaDB) のそれっぽい構成にする。

### オレオレ自己証明書を卒業する

いつの間にやら個人でも気軽に無料で(!) SSL 証明書を取得できるような世の中になっていた。[Let’s Encrypt](https://letsencrypt.org/ja/) という認証局である。実際には [Certbot](https://certbot.eff.org/) という ACME クライアントを使用することになる。


## やったこと 

### ネットワークの事前設定

* DDNS へのドメインの登録
* ルータのポートフォワーディングの設定

### OS インストールから Docker Compose のインストールまで

基本的に他の Qiita の記事の通りの実施で問題ないです。
地味にネットワークの設定ファイルの変更が初回起動の際に反映されておらず、netplan の設定ファイルの保存先を探したり、自分で netplan のコマンドを実行したりした。なんだよ netplan って。

### Redmine を動かす (やっと本題)

#### 設定のパクリ元

* [docker-compose.yml, ディレクトリ構成](https://blog1.mammb.com/entry/2020/05/24/090000)
* [ngix と certbot を Docker でどう動かすか](https://dzone.com/articles/nginx-and-https-with-lets-encrypt-certbot-and-cron) ※いつの間にか会員じゃないと見れなくなっている

#### ディレクトリ構成

ディレクトリ、ファイルの配置は以下の通り。

```
./
├── docker-compose.yml
├── redmine
│   ├── config.ru
│   ├── plugins
│   └── themes
└── reverse_proxy
    ├── Dockerfile
    ├── default.conf.template
    └── ssl

```

#### Redmine の設定をする

`docker-compose.yml` の内、AP サーバ (Redmine) の部分は以下の通り。

```yaml

x-DEFINE: &APP_RELATIVE_URL_ROO
  /redmine

x-DEFINE: &DB_PASSWORD
  redmine_passward

x-DEFINE: &TIME_ZONE
  Asia/Tokyo

（略）

  app:
    image: redmine:latest
    container_name: app
    ports:
      - 3000:3000
    volumes:
      - ./redmine/plugins:/usr/src/redmine/plugins
      - ./redmine/themes:/usr/src/redmine/public/themes
      - ./redmine/config.ru:/usr/src/redmine/config.ru
    environment:
      TZ: *TIME_ZONE
      REDMINE_DB_MYSQL: db
      REDMINE_DB_PASSWORD: *DB_PASSWORD
      RAILS_RELATIVE_URL_ROOT: *APP_RELATIVE_URL_ROO
    depends_on:
      - db
    restart: always
```

プラグインやテーマは必要に応じてボリュームでマッピングしているディレクトリに格納するようにする。
`config.ru` はサブディレクトリ運用する (例えば https://my.home.com/redmine でアクセスする) ために変更が必要な設定ファイルです。変更後内容は以下の通り。RAILS_RELATIVE_URL_ROOT の値は `docker-compose.yml` で定義している。

```ruby

# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment',  __FILE__)
map ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do
    run Rails.application
end
```

#### MariaDB の設定をする

`docker-compose.yml` の 内、DB サーバ (MariaDB) の部分は以下の通り。特筆すべき点は特にない。

```yaml
x-DEFINE: &DB_PASSWORD
  redmine_passward

x-DEFINE: &TIME_ZONE
  Asia/Tokyo

（略）
  db:
    image: mariadb:latest
    container_name: db
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: *DB_PASSWORD
      MYSQL_DATABASE: redmine
      TZ: *TIME_ZONE
    volumes:
      - ./data/db:/var/lib/mysql
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    restart: always
```

#### nginx + certbot の設定をする

リバースプロキシ (nginx) と ssl 証明書の取得 (certbot) の設定を行う。

`docker-compose.yml` の リバースプロキシの部分は以下の通り。

```yaml

x-DEFINE: &APP_RELATIVE_URL_ROO
  /redmine

(略)

  reverse_proxy:
    container_name: reverse_proxy
    build:
      context: ./reverse_proxy
      network: host
      args:
        - CERTBOT_EMAIL=xxxxx@yyyyy.zzzzz #replace with your own email
        - DOMAIN_LIST=my.home.com                #replace with your own domains
    environment:
      APP_ROOT: *APP_RELATIVE_URL_ROO
    volumes:
      - ./reverse_proxy/default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - "80:80"
      - "443:443"
    restart: always
```

リバースプロキシのイメージは nginx の公式イメージではなく、自分でカスタムしたものをビルドする。
イメージビルド時に渡す二つのパラメータ `CERTBOT_EMAIL` `DOMAIN_LIST` は Let's Encrypt に登録する連絡用メールアドレスと ssl 証明書を取得する対象のドメイン名である。

`environments` で指定しているのは Redmine が動くサブディレクトリ名である。
`volumes` で指定しているのは nginx の設定ファイルと、ssl 認証の格納先である。

リバースプロキシの `Dockerfile` の内容は以下の通りである。

```dockerfile

FROM nginx:latest
ARG CERTBOT_EMAIL=default@default.com
ARG DOMAIN_LIST

# Expose ports.
EXPOSE 80
EXPOSE 443

RUN  apt-get update \
      && apt-get upgrade -y \
      && apt-get install -y cron certbot \
      && certbot certonly --dry-run --standalone --agree-tos -m "${CERTBOT_EMAIL}" -n -d ${DOMAIN_LIST} \
     && certbot certonly  --standalone --agree-tos -m "${CERTBOT_EMAIL}" -n -d ${DOMAIN_LIST} \
      && rm -rf /var/lib/apt/lists/* \
      && echo "@monthly /usr/bin/certbot renew --nginx >> /var/log/cron.log 2>&1" >/etc/cron.d/certbot-renew \
      && crontab /etc/cron.d/certbot-renew 
VOLUME /etc/letsencrypt

CMD [ "sh", "-c", "cron && ./docker-entrypoint.sh nginx -g 'daemon off;'" ]
```

細かな説明は[パクリ元](https://dzone.com/articles/nginx-and-https-with-lets-encrypt-certbot-and-cron)を参照願う。
パクリ元から変更しているのは、

* `certbot` のインストールを `apt` で行うようにした。その影響で`certbot-auto` ではなくなっている。
* `entorypoint.sh` から `20-envsubst-on-templates.sh` が呼ばれるように `CMD`を変更 (ダサいとは思っているが他に思いつかない)

`default.conf.template` の内容は以下の通りである。
(https の設定は certbot の成功を一度確認してからコメントを外す)

```conf

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location ${APP_ROOT} {
        proxy_set_header X-Forwarded-Host $host:$server_port;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://app:3000${APP_ROOT}/;
    }

    # --- For CertBot ---
    location ^~ /.well-known/acme-challenge/ {
        root /usr/share/nginx/html/;
    }

    location = /.well-known/acme-challenge/ {
        return 404;
    }
    # -----------------

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

#server {
#   listen 443;
#   server_name shop.nureteni.awa.co.jp;
#
#    ssl on;
#    ssl_certificate /etc/letsencrypt/live/${MY_DOMAIN_NAME}.cert;
#    ssl_certificate_key /etc/letsencrypt/live/${MY_DOMAIN_NAME}.cert.key;
#
#    location ${APP_ROOT} {
#
#    proxy_set_header X-Forwarded-Host $host:$server_port;
#    proxy_set_header X-Forwarded-Server $host;
#    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#    proxy_pass http://app:3000${APP_ROOT}/;
#    }
#    # --- For CertBot ---
#    location ^~ /.well-known/acme-challenge/ {
#        root /usr/share/nginx/html/;
#    }
#
#    location = /.well-known/acme-challenge/ {
#        return 404;
#    }
#}

```

以上で  `docker-compose up -d` で必要なコンテナが立ち上がる。
あとは `https://my.home.com/redmine`で Redmine にアクセスできる。


## おわりに

細かいことに詰まることが多く、スキマ時間にやっていたけれども 2 週間近くの時間を消費することになった。
マイナーなユースケースだと思うが、この地球のどこかの人のためになればと思い、この記事を投稿した。