Written by TSUYOSHI

Docker環境で簡単にSSL化する方法を解説 【失敗しない初心者でもできる方法】

Docker

Dockerを導入していてSSL化して人「Dockerを導入してサイトを作っていたけど、本番環境で簡単にSSL化する方法はないのなか?」

こういった人向けにDockerでSSL化するimageを使った方法を紹介します。

本記事の内容

  • Dockerを使った環境でのSSL化をする方法を解説
  • ローカルでSSL化する
  • 本番環境サーバでSSL化する

この記事を書いている僕は、エンジニア歴4年。
現役のフリーランスエンジニアがお伝えするので、記事の信頼性担保に繋がると思います。フロントエンドがメインのエンジニアでありその視点で解説するので、Dockerがわかっていない駆け出しエンジニアにもわかりやすくお伝えします。

Dockerを使った環境でのSSL化をする

「jwilder/nginx-proxy」 「jrcs/letsencrypt-nginx-proxy-companion」 の Docker image を使う

さっそく結論ですが「jwilder/nginx-proxy」「jrcs/letsencrypt-nginx-proxy-companion」のDocker imageを使えば簡単です。
DockerでSSLを調べると
・jwilder/nginx-proxy
・jrcs/letsencrypt-nginx-proxy-companion
の組み合わせがよく出てきます。
今後困った時、つまづいたときに情報が多いので解決しやすい、今後もメンテナンスされて継続して利用できる可能性が高いということが選択の理由です。
Docker内のlinuxでcerbotを使ってSSL証明書を発行する操作などだと、手間がかかり、初心者にはわかりにくいです。設定だけで簡単にできる方法を今回紹介しています。

実際のコードで解説します。コピペでOKです。
手順を確認してからコードが書けるので、失敗せずにできると思います。

▼ローカル用のyaml

version: '3.7'

services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy-test
    privileged: true
    environment:
      - "DHPARAM_GENERATION=false"
    ports:
      - 80:80
      - 443:443
    networks:
      - container-link
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - proxy:/usr/share/nginx/html
      - proxy:/etc/nginx/vhost.d
      - ./docker/encrypt/certs:/etc/nginx/certs:ro
      # - ./conf.d/proxy.conf:/etc/nginx/conf.d/proxy.conf:ro # 設定を上書きするときはconf.d内に「.conf」拡張子で追加する
    restart: always
    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"

  letsencrypt-nginx:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt-nginx-test
    privileged: true
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy-test # volume_fromを使わない時はコンテナを指定する必要がある
    networks:
      - container-link
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - proxy:/etc/nginx/vhost.d
      - proxy:/usr/share/nginx/html
      - ./docker/encrypt/certs:/etc/nginx/certs:rw
    restart: always
    depends_on:
      - nginx-proxy

networks:
  default:
    external:
      name: bridge
  container-link:
    name: container_network

volumes:
  proxy:

詳細なコードは、githubに掲載しています。

github : https://github.com/it-web-life/docker_ssl_nginx_proxy

githubに上がっているリポジトリの「/development」以下がローカルで構築するときの例として記述しているコード例です。
/production 以下は本番を想定したコード例です。「/development」以下のコードの一部を本番用に書き換えた例を載せています。
ローカルではそのまま試して使えますが、本番では「/production」以下のコードを摘便コピペして使ってください。

次の項でさらに詳細を説明していきます。

構成の説明

概要を説明します。
nginxリバースプロキシでWebからのアクセスを80番(http)、443番(https)ポートをlistenします。そこからDockerネットワーク内で、接続します。

nginxリバースプロキシを使う理由は、先程も述べた通り、
1.cerbot等を使ったSSL化は複雑で操作が難しいので簡潔に設定だけで動作するため
(慣れていてもたまにしかやらないので忘れてイチイチ思い出すのに時間がかかる)
2.「jwilder/nginx-proxy」を使ったnginxリバースプロキシだと、マルチホスト(1つのサーバでサブドメイン含めて、複数のドメインを扱う)対応も楽にでき、拡張性がある。
「jrcs/letsencrypt-nginx-proxy-companion」を使う理由は、「jwilder/nginx-proxy」でも推奨しているimageでこちらも組み合わせとして使っている例が多く、Web上で参考記事が見つけやすいことが挙げられます。(参考記事は英語が多い)

使うimageはそれぞれ以下のような役割になります。

  • jwilder/nginx-proxy → Nginxリバースプロキシ
  • jrcs/letsencrypt-nginx-proxy-companion → Let’s EncryptでSSL化を自動化する仕組み

Webからのアクセスは一旦、nginxリバースプロキシ→Dockerネットワークに流す→Dockerネットワーク内でサービスを提供するコンテナで処理して表示となります。
今回の例では説明を簡潔にするため、WordPress(WordPressコンテナ+MariaDBコンテナ)を使います。
ここの構成を例えばNuxt + Express + MongoDBにするなどにする場合も方法としては同じです。

volume_fromは使わない

「jrcs/letsencrypt-nginx-proxy-companion」 の公式ではyamlバージョン2の記述で、yamlバージョン3から使用できなくなった「volume_from」を使っているので、「volume_from」は使わずVersion3の書き方にしましょう。
理由:サンプルがversion2で書かれているからか、volume_fromを使った例の記事がほとんどですが、最新のyaml Ver3の方が新しい書き方も併せて使うことが可能だったりするからです。

サンプルプログラムをもとに解説します。
環境変数の「NGINX_PROXY_CONTAINER」が用意されており、これを設定することによって「volume_from」を使わず書けるようになっています。
以下の「environment」の部分です。

  letsencrypt-nginx:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt-nginx-test
    privileged: true
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy-test # volume_fromを使わない時はコンテナを指定する必要がある

Volumeで「jwilder/nginx-proxy」と「jrcs/letsencrypt-nginx-proxy-companion」で、SSL証明書など共有すべきファイルをbind mountして共有します。
「nginx-proxy」サービスと「 letsencrypt-nginx」サービスの、volumesの項目がこれに該当します。

ローカルでSSL化する

SSL証明書をローカルで発行してみることが可能

ローカルでもSSL証明をを発行してhttps化することは可能です。

ローカルに認証局を立てて、その認証局を通してローカルでSSL化ができることが「jrcs/letsencrypt-nginx-proxy-companion」を使って、簡単にできるためです。

「jrcs/letsencrypt-nginx-proxy-companion」を使った例をあげます。
まずは動作させてみます。

githubコードの例で説明していきます。「/development」(ローカル環境の例)からsharedディレクトリ(「/development/shared」ディレクトリ)に移動します。

Nginxリバースプロキシ(nginx-proxy)の起動

docker-compose up -d nginx-proxy」で、Nginxリバースプロキシを起ち上げます。

$ docker-compose up -d nginx-proxy

Creating network "container_network" with the default driver
Creating volume "shared_proxy" with default driver
Creating nginx-proxy ... done

docker ps」で問題なく立ち上がっていることを確認します。

$ docker ps

CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                                      NAMES
b6c86833764c        jwilder/nginx-proxy   "/app/docker-entrypo…"   7 seconds ago       Up 6 seconds        0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   nginx-proxy

この状態で、「http://localhost/」 にアスセスすると「503 Service Temporarily Unavailable nginx/1.17.6」というような表示が出て、nginxが立ち上がっていることが確認できます。

WordPressの起動

次に、「wordpress」ディレクトリ(githubの例だと「/development/wordpress」ディレクトリ)に移動して、「docker-compose up -d」します。

$ docker-compose up -d

Creating volume "wordpress_db_test_mysql" with default driver
Pulling mysql (mariadb:)...
(中略)
Status: Downloaded newer image for wordpress:latest
Creating wp-test-mariadb ... done
Creating wp-test-wordpress ... done

WordPressが立ち上がりました。「docker ps」でも立ち上がっていることを確認します。

$ docker ps
```
CONTAINER ID        IMAGE                 COMMAND                  CREATED              STATUS              PORTS                                      NAMES
69488cefe56e        wordpress             "docker-entrypoint.s…"   24 seconds ago       Up 23 seconds       80/tcp                                     wp-test-wordpress
605344246c5b        mariadb               "docker-entrypoint.s…"   24 seconds ago       Up 23 seconds       0.0.0.0:3306->3306/tcp                     wp-test-mariadb
b6c86833764c        jwilder/nginx-proxy   "/app/docker-entrypo…"   About a minute ago   Up About a minute   0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   nginx-proxy

次に`localhost`にブラウザでアクセスすると、WordPressのセットアップ画面になっていると思います。
適当でよいので必要事項を設定してWordPressが立ち上がるようにします。自身でサービスを作っている場合は摘便置き換えて読んでください。

http://localhost/」 にアクセスするとWordPressが立ち上がりました。

この時点では 「https://localhost/」 にアクセスしても「このサイトにアクセスできません」となります

Let’s Encrypt設定をするコンテナの起動 (letsencrypt-nginx)

sharedディレクトリ(「/development/shared」ディレクトリ)に移動します。

docker-compose up -d letsencrypt-nginx」 でletsencrypt-nginxサービスを起ち上げます。

$ docker-compose up -d letsencrypt-nginx

Pulling letsencrypt-nginx (jrcs/letsencrypt-nginx-proxy-companion:)...
latest: Pulling from jrcs/letsencrypt-nginx-proxy-companion
(中略)
nginx-proxy is up-to-date
Creating letsencrypt-nginx ... done

docker ps」コマンドで起動を確認します。

$ docker ps

CONTAINER ID        IMAGE                                    COMMAND                  CREATED             STATUS              PORTS                                      NAMES
3c5f66095ed9        jrcs/letsencrypt-nginx-proxy-companion   "/bin/bash /app/entr…"   19 seconds ago      Up 18 seconds                                                  letsencrypt-nginx
69488cefe56e        wordpress                                "docker-entrypoint.s…"   8 minutes ago       Up 8 minutes        80/tcp                                     wp-test-wordpress
605344246c5b        mariadb                                  "docker-entrypoint.s…"   8 minutes ago       Up 8 minutes        0.0.0.0:3306->3306/tcp                     wp-test-mariadb
b6c86833764c        jwilder/nginx-proxy                      "/app/docker-entrypo…"   9 minutes ago       Up 9 minutes        0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   nginx-proxy

letsencrypt-nginx が立ち上がりました。

localhost にアクセスすると https://localhost/ へリダイレクトされるようになり、httpsのページが表示されます。

ただ擬似的なSSL化でローカル認証局を立てている状態であり、Chromeだと開けないので、MacならSafariで開いて「このサイトを閲覧」をクリックすると https://localhost/ が開けるようになります。

このままだと開発しにくければ、letsencrypt-nginx の設定は落として開発してもよいかもしれません。
※私は通常は作っているサービス側(今回でいうとWordPressコンテナ)で、80番ポートでlistenする設定をして開発し、本番アップの際にリバースプロキシ・Let’s Encryptをを追加構築しています。

「letsencrypt-nginx」を無効にする方法

ローカルでSSL化しましたが、無効にしてもとに戻す方法を記載していきます。まず、`letsencrypt-nginx`を落とします。

$ docker-compose stop letsencrypt-nginx

リダイレクトされないように、wordpressのコンテナ設定に
– HTTPS_METHOD=noredirect
を追記します。

wordpressディレクトリ(githubコードでいう「/development/wordpress」ディレクトリ)に移動して「docker-compose up -d」します

http://localhost でアクセスできるように戻りました
※Chromeだとハードキャッシュクリア(ChromeDevToolsをCommand+option+i で起ち上げて、リロードボタンを右クリックして「キャッシュの消去とハード再読み込み」クリック)しないと表示が戻らないかも知れません。
※うまくいかないときは、すべてのコンテナを「docker-compose down」で落としてから再度、「docker-compose up -d 【起ち上げたいコンテナ名】」で起ち上げ直してみてください。

ローカル認証局(オレオレSSL証明書発行)の仕組み

通常はサーバでは、Let’s EncryptなどでSSLの証明書発行して認証をします。
ローカル環境のlocalhostではこれはできないため、ローカルに認証局を立てて、ローカルでSSL証明書を発行し、擬似的にSSL化した環境を作ります。
単純に、今回でいうWordPressコンテナの「docker-compose.yml」で定義している
– CERT_NAME=default
に記載した認証キーが発行されてローカルでSSL化できている状態を「jrcs/letsencrypt-nginx-proxy-companion」が作りだしています。
default.key, defalut.dhparam.pem といったファイルがローカルに作成されています。

本番環境サーバでSSL化する際の方法と注意点

まずはローカルで構築したコードを必要な部分だけ、本番サーバにアップロードします。
(githubの例だと、「/production」ディレクトリの内容)
レンタルサーバだとdockerを動かせないと思うので、VPSを使います。
例えば「さくらVPS」や「GMO VPS」を私はよく使います。AWSのLightsailなどもよいかもしれません。

  • サーバの初期設定をしていない場合は、dockerをインストールするなどの対応をします。
  • 設定を変更した本番用のdocker-compose.yml ファイルをサーバにアップロードします。
  • この後の手順概要としては、shared, wordpress(摘便読み替えてください)ディレクトリで「docker-compose up -d」して、Dockerを起ち上げます。

まずはLETSENCRYPT_TEST= trueの状態で組む

まずはサーバ上でローカル認証局を立てて見る方がよいです。

Let’s Encryptは発行回数に制限があるので、間違えないよう順番に進める方が確実なためです。

本番対応はローカルで動いているなら、設定を変えるだけで簡単にできます。

ローカルで設定した状態から、「LETSENCRYPT_TEST=true」以外の部分を本番用設定に書き換えます。

本番サーバでローカル認証局を作って、SSL化してまずは試す

・今回の例だと、WordPressコンテナの以下設定を変更します。

environment
  - VIRTUAL_HOST=localhost # →「example.com,www.example.com」等、自分のドメインにします。
  - LETSENCRYPT_HOST=localhost # →「example.com,www.example.com」等、自分のドメインにします。

既にサーバ上のDNS設定は完了している前提で話を進めます。

この状態で、順番にコンテナを起ち上げていきます。(順番はどちらが先でもOKです)
sharedディレクトリに移動して、「docker-compose up -d」を実行。
nginx-proxyコンテナ、letsencrypt-nginxコンテナを起ち上げます。

wordpressディレクトリに移動して「docker-compose up -d」を実行。
wordpressコンテナ、mariadbコンテナを起ち上げます。

これでサーバ上で、ローカル認証局のSSL化が出来ているはずです。
MacならSafariで開いて「このサイトを閲覧」をクリックすると、SSL化した状態で閲覧ができると思います。

本番対応する

あとは設定を変更して、Let’s EncryptでSSL化するだけです。

  • CERT_NAME=xxx 」を削除(コメントアウト)
  • LETSENCRYPT_TEST= false 」にする
  • docker-compose up -d 」する

すでにローカル認証局を立てて試しているので、設定を変更するだけなので、失敗はしないと思います。
注意点としては、Let’s Encryptは1週間に5回までしか証明書発行がトライできないため、間違えないように慎重にやりましょう。
何度も間違えると証明書が発行できなくなり、1週間後にやり直しになっていまいます。

本番サーバでSSL化する

今回でいうWordPressコンテナの以下設定を変更します。

environment環境変数にて、

  • – VIRTUAL_HOST=localhost」 だった部分を
    – VIRTUAL_HOST=example.com,www.example.com
    等に変更します。nginx-proxyに設定するバーチャルホスト設定です。
  • – LETSENCRYPT_HOST=localhost」 だった部分を
    – LETSENCRYPT_HOST=example.com,www.example.com
    等に変更します。letsencrypt-nginxに設定するLet’s Encrypt用の設定です。
  • 「- LETSENCRYPT_EMAIL=xxxx@gmail.com」は、Let’s Encrypt設定で使うメールアドレスなので、自分のメールアドレスを設定してください。
  • – LETSENCRYPT_TEST=true」→ ここは「– LETSENCRYPT_TEST=false」にしてください。設定しないとテストのままになり、Let’s Encryptの設定に切り替わりません。
  • – CERT_NAME=default
    → 本番ではこの行はコメントアウト(削除)します。ローカル認証局起ち上げで使うだけなためです。
  • – HTTPS_METHOD=noredirect
    → httpからhttpsへのリダイレクトを無効にする設定なので、 httpsへリダイレクトでよければこの行はコメントアウト(削除)します。通常は常にhttpsへリダイレクトにすべきだと思うので、コメントアウトします。

まとめると以下になります。

▼ shared/docker-compose.yml

version: '3.7'

services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    privileged: true
    environment:
      - "DHPARAM_GENERATION=false"
    ports:
      - 80:80
      - 443:443
    networks:
      - container-link
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - proxy:/usr/share/nginx/html
      - proxy:/etc/nginx/vhost.d
      - ./docker/encrypt/certs:/etc/nginx/certs:ro
      # - ./conf.d/proxy.conf:/etc/nginx/conf.d/proxy.conf:ro # 設定を上書きするときはconf.d内に「.conf」拡張子で追加する
    restart: always
    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"

  letsencrypt-nginx:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt-nginx
    privileged: true
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy # volume_fromを使わない時はコンテナを指定する必要がある
    networks:
      - container-link
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - proxy:/etc/nginx/vhost.d
      - proxy:/usr/share/nginx/html
      - ./docker/encrypt/certs:/etc/nginx/certs:rw
    restart: always
    depends_on:
      - nginx-proxy

networks:
  default:
    external:
      name: bridge
  container-link:
    name: container_network

volumes:
  proxy:

▼ wordpress/docker-compose.yml

version: "3.7"

services:
  wordpress:
    image: wordpress #WordPressのイメージ
    container_name: wp-prod-wordpress
    # ports: # ポートの指定はnginx-proxyでするのでここではしない
    #   - "80:80"
    networks:
      - container-link
    volumes:
      - ./html:/var/www/html #docker_wordpress/html にデータがマウントされる
    environment:
      - WORDPRESS_DB_NAME=wordpress_db #WordPressの設定
      - WORDPRESS_DB_PASSWORD=mysql_password #WordPressの設定
      - VIRTUAL_HOST=example.com,www.example.com # nginx-proxyのホスト設定 ※example.comを書き換えてください
      - LETSENCRYPT_HOST=example.com,www.example.com # Let's Encryptのホスト設定 ※example.comを書き換えてください
      - LETSENCRYPT_EMAIL=xxxx@gmail.com # Let's Encryptのメール設定
      - LETSENCRYPT_TEST=false # Let's Encryptのテストかどうかのフラグ 本番ではfalseにする (指定しないとテスト扱いになる)
      # - CERT_NAME=default # ローカルで認証局を立てるときに使う ※本番ではLet's Encryptから直接取得するのでコメントアウトする
      # - HTTPS_METHOD=noredirect # リダイレクトを無効にする # 必要に応じて有効化
    depends_on:
      - mysql
    restart: always

  mysql:
    image: mariadb #MariaDBのイメージ
    container_name: wp-prod-mariadb
    ports:
      - "3306:3306" #MacのMySQLとポートがバッティングする場合は変える 例) - "33060:3306"
    networks:
      - container-link
    volumes:
      - db_test_mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: mysql_password
    restart: always

networks:
  default:
    external:
      name: bridge
  container-link:
    name: container_network

volumes:
  db_test_mysql:

数分で自動的にLet’s Encriptから証明書を取得してSSLでそのドメインにアクセスできるようになります。

以上となります。お疲れ様でした!