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


やりたいこと

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

やろうと思ったこと

wrike も考えた

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

planio を使おうと思った

無料プランではタグを使うためのプラグインを入れられない。有料プランは個人で使うには高すぎる。

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

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

やることになったこと

自宅サーバを立てる

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

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

Docker Compose を使う

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

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

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

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

やったこと

ネットワークの事前設定

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

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

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

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

設定のパクリ元

ディレクトリ構成

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

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

Redmine の設定をする

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


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 で定義している。


# 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) の部分は以下の通り。特筆すべき点は特にない。

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 の リバースプロキシの部分は以下の通り。


x-DEFINE: &APP_RELATIVE_URL_ROO
  /redmine

(略)

  reverse_proxy:
    container_name: reverse_proxy
    build:
      context: ./reverse_proxy
      network: host
      args:
        - [email protected] #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 の内容は以下の通りである。


FROM nginx:latest
ARG [email protected]
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;'" ]

細かな説明はパクリ元を参照願う。
パクリ元から変更しているのは、

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

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


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 週間近くの時間を消費することになった。
マイナーなユースケースだと思うが、この地球のどこかの人のためになればと思い、この記事を投稿した。