【Docker】Webサイトを無料でSSL(HTTPS)化する方法

DockerコンテナでWebサイトを構築し、SSL(HTTPS)化する手順をまとめました。
Dockerで構築したWebサイトの構成は以下となります。

CentOS8環境(ConoHaVPS

  • Nginx(https-portal)
  • Django
  • PostgreSQL

https-portalは、dockerで構築したWebサイトを自動でSSL化(https)にするコンテナで、Nginxと無料のSSL(Let’s Encrypt)が内包されています。

Docker上でNginx+Django+PostgreSQLの構築については以下ページを参考にしてください。

設計手順は以下となります。

「Docker-compose」でNginx+Django+PostgreSQLのWebサイトを無料でSSL化する手順

今回構築するプロジェクトの構造は以下となります。

$ tree
.
|-- Dockerfile
|-- db-data
|-- docker-compose.yml
|-- manage.py
|-- mysite
|   |-- __init__.py
|   |-- __pycache__
|   |   |-- __init__.cpython-38.pyc
|   |   |-- settings.cpython-38.pyc
|   |   |-- urls.cpython-38.pyc
|   |   `-- wsgi.cpython-38.pyc
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|   `-- wsgi.py
|-- requirements.txt
`-- ssl_certs
    |-- account.key
    |-- dhparam.pem
    `-- engineers-life.com
        `-- production
            |-- chained.crt -> /var/lib/https-portal/example.com/production/signed.crt
            |-- domain.csr
            |-- domain.key
            `-- signed.crt

事前に必要となるファイルやディレクトリは以下となります。

  • Dockerfile
  • requirements.txt
  • docker-compose.yml

各ファイルで内のデータは以下で説明します。

Dockerfile

FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir ./code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
RUN pip install django-sslserver
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
COPY . /code/

Dockerfileはpythonの最新版Dockerイメージをインストールし、requirements.txt内のイメージをコンテナ上でインストールします。

requirements.txt

Django
psycopg2
gunicorn

requirements.txtでコンテナ上でインストールするイメージとなります。

  • Django:Webフレームワーク
  • psycopg2:PythonからPostgreSQLサーバにアクセスするために必要となるイメージ
  • gunicorn:NginxでPython製WebフレームワークでできたWebアプリケーションを起動

docker-compose.yml

docker-compose.ymlで設定する内容は以下となります。

version: '3'
 
services:
  db:
    image: postgres
    ports:
            - 5432:5432
    environment:
            - POSTGRES_PASSWORD=postgres
    volumes:
            - ./db-data:/var/lib/postgresql/data
    container_name: postgres

  web:
    restart: always
    build: .
    command: bash -c "python manage.py runserver 0.0.0.0:8000 && python manage.py migrate"
    volumes:
            - .:/code
    ports:
            - 8000:8000
    depends_on:
            - db
    container_name: django

  https-portal:
    image: steveltn/https-portal:1
    ports:
            - 80:80
            - 443:443
    links:
            - web
    restart: always
    environment:
      DOMAINS: 'example.com -> http://web:8000'
      STAGE: 'production' # Don't use production until staging works
      #FORCE_RENEW: 'true'
    container_name: https-portal
    volumes:
            - ./ssl_certs:/var/lib/https-portal

volumes:
    db-data:
    ssl_certs:

上記設定はあくまで検証環境となります。
グローバルに公開する際はポート「8000番」閉じて公開するようお願いします。

service「db」

PostgreSQLのコンテナを構築します。
ポート番号は5432にアクセスします。データベースは永続化するために「db-data」へ共有します。
コンテナ名は「postgres」になります。

service「web」

DjangoWebアプリを起動します。
ポートは8000番でアクセスします。
コンテナはdbコンテナが立ち上がったことを確認した後、コンテナを起動するようにします。
コンテナ名は「django」となります。

service「https-portal」

Nginxを起動し、無料SSL(Let’s Encrypt)を自動で設定します。
80番ポートでアクセスするとリダイレクトで443へアクセスします。
80もしくは443でアクセスした場合、django(web)8000番へリダイレクトします。

「STAGE」では以下の設定をします。
インターネット上では以下の動作をします。

  • local:オレオレ証明書 アクセスできない
  • staging:オレオレ証明書 アクセスできる
  • production:証明書発行 アクセスできる

「#FORCE_RENEW: ‘true’」ここで無効化をしています。
docker-compose up/downを繰り返しているとrenewが実行され、リミットエラーが発生してしまいます。
月に5回ほどこれをしてしまうと、1週間証明書が発行できなくなり、SSL化できなくなってしまいます。

Webサイト構築後に「#」を外すようにしましょう。
これにより、定期的に証明書の更新を自動で実施してくれます。

「ssl_certs」ではコンテナボリュームの証明書と共有化してくれます。
ボリューム名は「ssl_certs」とします。

「docker-compose」でDjangoプロジェクト作成

ファイルの作成が完了したら、次に対象のフォルダ直下で以下コマンドでDjangoプロジェクトを作成します。

$ docker-compose run web django-admin startproject mysite .

※docker-composeコマンドが使えない場合はパッケージがインストールされていないので事前にインストールしておきましょう。

正常に実行されると以下のようにフォルダとファイルが作成されます。

-rw-r--r--  1 root             root  247 May  7 13:45 Dockerfile
drwx------ 19 systemd-coredump root 4096 May  8 17:18 db-data
-rw-r--r--  1 root             root  951 May  8 17:12 docker-compose.yml
-rwxr-xr-x  1 root             root  626 May  5 00:48 manage.py
drwxr-xr-x  3 root             root 4096 May  5 01:05 mysite
-rw-r--r--  1 root             root   25 May  5 00:39 requirements.txt
drwxr-xr-x  3 root             root 4096 May  8 17:16 ssl_certs

DjangoをPostgreSQLデータベースに接続

Djangoのデータベース接続をPostgreSQLに設定します。
プロジェクトディレクトリで、「mysite/settings.py」ファイルを編集します。

vi mysite/settings.py
#ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["*"]
  
#DATABASES = {
#    'default': {
#        'ENGINE': 'django.db.backends.sqlite3',
#        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#    }
#}
  
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD' : 'postgres',
        'HOST' : 'db',
        'PORT' : 5432,
    }
}

ALLOWED_HOSTSはWebサイトへのアクセスを全て許可「*」とします。
DATABASESはPostgreSQLへのアクセスへ書き換えます。

データベース設定後、Djangoコンテナで以下コマンドで更新を実施します。

docker-compose run web python manage.py migrate

「docker-compose」でWebサイト起動とSSL自動取得・設定

docker-compose up

------中略------


django          | Django version 3.0.6, using settings 'mysite.settings'
django          | Starting development server at http://0.0.0.0:8000/
django          | Quit the server with CONTROL-C.

「docker-compose up」コマンド実行後、上記のように表示された後、少し経つとSSL証明書の発行と設定が自動で実施されます。

正常に動作すると以下のようにサービスが立ち上がり、SSL(https)でのアクセスが可能となります。

https-portal    | [services.d] starting services
https-portal    | [services.d] done.

ブラウザを立ち上げ、該当のWebサーバへアクセスすると以下のように証明書が有効となり、SSL化されていることが確認できます。

証明書も正常に設定されていることが確認できます。

「docker-compose up」後の状態

Dockerコンテナ起動後の状態は以下のようになります。

$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
django_web              latest              cbfffd069c7b        47 hours ago        1.02GB
python                  3                   4f7cd4269fa9        10 days ago         934MB
postgres                latest              0f10374e5170        2 weeks ago         314MB
steveltn/https-portal   1                   fcf3e18b11ed        8 weeks ago         301MB
$ 
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                                      NAMES
26a98361c225        steveltn/https-portal:1   "/init"                  17 minutes ago      Up 2 seconds        0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   https-portal
42d4fc615990        django_web                "bash -c 'python man…"   17 minutes ago      Up 3 seconds        0.0.0.0:8000->8000/tcp                     django
8bb99b4ac06c        postgres                  "docker-entrypoint.s…"   17 minutes ago      Up 4 seconds        0.0.0.0:5432->5432/tcp                     postgres
$ 
$ docker volume ls
DRIVER              VOLUME NAME
local               django_db-data
local               django_ssl_certs
$ 

証明書実行時にエラーが発生した場合

上記でも簡単に説明はしましたが、今回dockerコンテナで起動した「https-portal」ですが、NginxとSSLを自動で立ち上げてくれますが、「FORCE_RENEW」を設定すると、何度か、「docker-compose」のUP/DOWNを繰り返すと、リミッターに抵触し、1週間ほど証明書が上手く設定できなくなってしまいます。

https-portal_1  | Response Code: 429
https-portal_1  | Response: {u'status': 429, u'type': u'urn:ietf:params:acme:error:rateLimited', u'detail': u'Error creating new order :: too many certificates already issued for exact set of domains: test.engineers-life.com: see https://letsencrypt.org/docs/rate-limits/'}
https-portal_1  | ================================================================================
https-portal_1  | Failed to sign test.engineers-life.com, is DNS set up properly?
https-portal_1  | ================================================================================
https-portal_1  | Failed to obtain certs for test.engineers-life.com

調べてみるとLet’s Encrypyでは証明書の更新は週5回までとなっており、それ以上更新することがNGとなっているので、ある程度構築が完了した後、「FORCE_RENEW」を設定し反映するようにしましょう。
もしくは、今回の無料SSLは3ヶ月更新のため、3ヶ月後の更新時に「FORCE_RENEW」を反映し、更新を実施します。

これ、私もハマってしまい、SSL化できなくなってしまいました。
リミットは1週間ほどで外れ、再度更新ができるようなので、ここは気をつけて運用するようにしましょう。

自動SSL化コンテナ「https-portal」の公式サイトは以下となります。
https://github.com/SteveLTN/https-portal#quick-starthttps://github.com/SteveLTN/https-portal#quick-start

以上がNginx+Django+PostgreSQL構成のWebサイトをSSL化する手順となります。




エンジニアのオンライン学習

ITエンジニアにおすすめの教材、オンラインスクールです。
無料からエンジニアの学習ができる教材などまとめているので参考にしてください。

おすすめオンライン教材
自宅で学習ができるオンラインスクール

ITエンジニアの開発・検証・学習としてインターネット上で専用のサーバ(VPS)を利用しましょう!
実務経験はVPSで学べます。



6件のコメント

webのコンテナに対して8000番のポートフォワードを設定する必要はないのではないでしょうか?
せっかくリバースプロキシでHTTPS化してるのにセキュリティホールを作ってしまっているようなものだと思いますが。
(ついでに言えば、dbも直接外部公開するのはあまり良くないと思いますが…)

surface0さん
コメント有難うございます。
おっしゃる通りですね。

こちらのページの設定はあくまで、当時のDjango検証用で構築したサイトのデモとなるので、参考としてお願いします。
DBに関してもWebアプリを構築するうえで、簡易的な設定となります。

実際商用で使用する際は、セキュリティも考慮したうえでの設計をお願いします。

返信ありがとうございます。

ローカル環境で試しに、というのであれば許容するところのなのですが、
VPSという一般公開される環境において、しかも入門記事といえど特にHTTPS導入というセキュリティ向上がテーマでの記事ですから、個人・商用問わず最低限のセキュリティには気を配った説明をお願いしたいと存じます。

発展記事ならば尚のことで「8000番を開けておくのはもう不要です。危険なので閉じましょう」というのが入っていれば説得力があって良いかと思いました。

このような丁寧な説明の記事ほど鵜呑みにしてしまう初学者は多いので、書いてない事についての意図が伝わりづらく難しいところだとは思いますが、なるべくご配慮いただけると幸いです。

kさん
コメントありがとうございます。

私は一時期商用として公開していましたよー
ただし、FWなどのセキュリティ対策はしていました。

公式が非推奨と言っているのかは確認できませんでしたが、個人で運用するのがめんどくさいので結果的にWordpressで運用してます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

ABOUT US
げんき☆ひろき
インターネット関連のSEをやっています。 ネットワーク、サーバー、ストレージ、仮想基盤まで幅広く手を出しており、MVNOの構築経験もあります。 現在は、Pythonを使ったプログラミングの開発をしネットワークの自動化ツールを作成しています! Pythonの入門書も作成しているので、ぜひ参考にしてください!