13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docker環境を1台のサーバーで共存させる:80/443ポート競合を解決する実践構成

13
Posted at

1台のサーバーで Dev / Staging を同時運用する方法

Docker + Nginx Reverse Proxy + 自動SSL(実践構成)

はじめに

スタートアップや小規模開発チームでは、次のような課題に直面することがよくあります。

  • Development 環境と Staging 環境を同時に動かしたい
  • ただし、サーバーコストはできるだけ抑えたい
  • 1台の VPS / EC2 / 物理サーバーで運用したい
  • 両方とも Docker ベースで動いている
  • HTTPS もきちんと対応したい

ここで必ず起きる問題があります。

Dev も Staging も 80 / 443 ポートを使いたい

当然ながら、後から起動したコンテナはポート競合で立ち上がりません。

そこで今回は、実務でも使いやすい構成として、

  • Nginx Reverse Proxy
  • ドメインごとの振り分け
  • Let's Encrypt + Certbot によるSSL自動更新
  • Dev / Staging の独立デプロイ

を実現する方法を、わかりやすく整理して紹介します。

全体構成

Internet
   │
   │ 80 / 443
   ▼
┌─────────────────────────────┐
│        nginx_proxy          │
│ Reverse Proxy + SSL Gateway │
└──────────┬──────────────────┘
           │
     ┌─────┴─────┐
     │           │
     ▼           ▼
┌──────────┐ ┌──────────┐
│ web_dev  │ │ web_stg  │
└──────────┘ └──────────┘

ポイント

外部公開されるのは nginx_proxy のみです。

  • 80:80
  • 443:443

各アプリケーションコンテナは内部ネットワーク上で通信し、公開ポートは不要です。

Nginx が Host ヘッダーを見て振り分けます。

  • dev.example.com → Development
  • stg.example.com → Staging

事前準備

以下を用意しておきます。

  • ドメイン(またはサブドメイン)
  • DNS A レコード
  • 80 / 443 ポート開放

例:

dev.example.com -> 1.2.3.4
stg.example.com -> 1.2.3.4

Step 1: 共通 Docker Network を作成

Proxy と各アプリが別々の compose 管理でも通信できるように、共通ネットワークを作成します。

docker network create proxy-network

各アプリ側の docker-compose.yml

services:
  web_dev:
    networks:
      - proxy-network

networks:
  proxy-network:
    external: true

Staging 側も同様です。

services:
  web_stg:
    networks:
      - proxy-network

補足

web_devweb_stg という service 名が、そのまま Nginx から参照されるホスト名になります。

Step 2: Reverse Proxy 用 Stack を作成

ディレクトリ構成:

nginx-proxy/
├── docker-compose.yml
├── nginx.conf
├── certs/
└── certbot_webroot/

docker-compose.yml

version: "3.8"

services:
  nginx_proxy:
    image: nginx:alpine
    container_name: nginx_proxy
    restart: unless-stopped

    ports:
      - "80:80"
      - "443:443"

    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/letsencrypt
      - ./certbot_webroot:/var/www/certbot

    networks:
      - proxy-network

  certbot:
    image: certbot/certbot:latest
    container_name: certbot_proxy
    restart: unless-stopped

    volumes:
      - ./certs:/etc/letsencrypt
      - ./certbot_webroot:/var/www/certbot

networks:
  proxy-network:
    external: true

なぜ Proxy を分離するのか

1. デプロイを独立できる

アプリ更新時に Proxy を触る必要がありません。

2. 将来的な拡張がしやすい

  • QA
  • Preview
  • Demo 環境

などを追加しやすくなります。

3. SSL 管理を集約できる

証明書管理が1箇所で済みます。

Step 3: Nginx 設定

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;

    real_ip_header X-Forwarded-For;
    set_real_ip_from 172.16.0.0/12;
    real_ip_recursive on;

    server {
        listen 80;
        server_name dev.example.com stg.example.com;

        location /.well-known/acme-challenge/ {
            root /var/www/certbot;
        }

        location / {
            return 301 https://$host$request_uri;
        }
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name dev.example.com;

        ssl_certificate     /etc/letsencrypt/live/dev.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/dev.example.com/privkey.pem;

        location / {
            proxy_pass http://web_dev:80;

            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name stg.example.com;

        ssl_certificate     /etc/letsencrypt/live/stg.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/stg.example.com/privkey.pem;

        location / {
            proxy_pass http://web_stg:80;

            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

実務で重要なポイント

クライアントIPを正しく取得する

Reverse Proxy 配下では、設定しないとバックエンド側で本来のアクセス元IPが取得できません。

そのため以下は非常に重要です。

real_ip_header X-Forwarded-For;

Step 4: 初回SSL発行

初回は HTTP Challenge を利用して証明書を取得します。

Dev 用

docker run --rm \
  -v ./certs:/etc/letsencrypt \
  -v ./certbot_webroot:/var/www/certbot \
  certbot/certbot certonly \
  --webroot -w /var/www/certbot \
  -d dev.example.com

Staging 用

docker run --rm \
  -v ./certs:/etc/letsencrypt \
  -v ./certbot_webroot:/var/www/certbot \
  certbot/certbot certonly \
  --webroot -w /var/www/certbot \
  -d stg.example.com

取得後に起動します。

docker compose up -d

よくあるトラブル

502 Bad Gateway

  • コンテナ未起動
  • service 名の誤り
  • network 未接続

SSL 認証失敗

  • DNS未反映
  • Port 80 閉塞
  • challenge path ミス

ポート競合

既にホスト側で Nginx / Apache が起動しているケースがあります。

この構成が向いているケース

  • 小規模チーム
  • MVP 開発
  • 社内システム
  • Staging / QA 環境
  • コスト最適化したい案件

まとめ

1台のサーバーでも、設計次第で

  • Development
  • Staging

を安全かつスマートに共存させることができます。

さらに、

  • HTTPS対応
  • SSL自動更新
  • 独立デプロイ
  • ポート競合回避

まで実現できます。

小さく始めて、必要になれば後からスケールする。

その第一歩として、とても扱いやすい構成です。

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?