Dify や n8n のようなアプリを VPS で動かすとき、最初に決めるのが「どうやって HTTPS で公開するか」です。この記事では、どのアプリにも使える基本の形として、Caddy を前に置き、アプリ本体は外に出さない構成を作ります。
できあがる構成は次のとおりです。外から届くのは Caddy の 80番と443番だけで、アプリのポートはサーバーの中だけで使います。
インターネット ──(80/443)──> Caddy ──(Docker の内部ネットワーク)──> アプリ
Caddy を選ぶ理由
Caddy は、設定ファイルにドメイン名を書くだけで、証明書の取得と更新、HTTP から HTTPS への転送を自動で行います。公式ドキュメントによると、条件は次の4つです。
- ドメインの A レコード(IPv6 なら AAAA レコード)がサーバーを向いている
- 80番と443番のポートが外から届く
- Caddy がその2つのポートを使える
- Caddy のデータディレクトリが書き込み可能で、消えずに残る
Nginx と certbot を組み合わせる方法もありますが、証明書の更新を別に管理する必要があります。1台の VPS で個人がアプリを動かすなら、Caddy のほうが管理する部品が少なくて済みます。
前提
- Ubuntu 24.04 LTS の VPS(22.04、26.04 でも同じ手順で入れられると Docker 公式は案内しています)
- 独自ドメイン(サブドメインでも構いません)
- SSH でログインできること
手順1:ファイアウォールを設定する
SSH、HTTP、HTTPS だけを通します。HTTP/3 を使えるように、443番は UDP も開けます。
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443
sudo ufw enable
sudo ufw status
VPS の会社によっては、管理画面にもファイアウォール(パケットフィルターやセキュリティグループと呼ばれます)があります。その場合は、管理画面の側でも同じ3つを許可してください。
Docker のポート公開は ufw を素通りします
Docker 公式ドキュメントは、Docker と ufw は「互換性がない」と明記しています。ports: で公開したポートへの通信は、ufw のルールが適用される前に Docker 側に振り分けられるためです。ufw で塞いだつもりのポートでも、ports: に書けば外から届きます。
対策は、外に出すポートを Caddy の 80番と443番だけにすることです。アプリ側は ports: ではなく expose: を使うか、どうしてもサーバーに公開する場合は 127.0.0.1:8080:80 のように 127.0.0.1 に限定します。
手順2:Docker をインストールする
Docker 公式の apt リポジトリから入れます。Ubuntu 標準の docker.io パッケージなどが入っている場合は、先に削除するよう公式は案内しています。
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)
公式の GPG 鍵とリポジトリを登録します。
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
Docker 本体と Compose プラグインを入れて、動作を確認します。
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run hello-world
docker compose version
この手順を Ubuntu 24.04 のコンテナで実行したところ、2026年10月21日の時点で Docker Engine 29.9.2 と Docker Compose v5.6.1 が入りました。
手順3:DNS を設定する
ドメインの管理画面で、公開したいホスト名の A レコードに VPS の IP アドレスを設定します。Caddy は起動したときに証明書を取りにいくため、DNS の設定を先に済ませておきます。
手順4:Compose ファイルと Caddyfile を作る
作業用のディレクトリを作り、次の3つのファイルを置きます。例として、アクセス元の情報を表示するだけの小さなアプリ(traefik/whoami)を公開します。実際に使うときは、app の部分を自分のアプリに置き換えます。
compose.yaml:
services:
caddy:
image: caddy:2.11.7
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
environment:
- SITE_DOMAIN=${SITE_DOMAIN}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
app:
image: traefik/whoami:v1.11.0
restart: unless-stopped
# 外には公開せず、Caddy からだけ届くようにする
expose:
- "80"
volumes:
caddy_data:
caddy_config:
Caddyfile:
{$SITE_DOMAIN} {
reverse_proxy app:80
}
.env(自分のドメインに置き換えます):
SITE_DOMAIN=app.example.com
ポイントは3つです。
- イメージのバージョンを固定する:
caddy:latestのように書くと、再起動したときに意図せず新しい版に変わることがあります。 caddy_dataをボリュームにする:取得した証明書がここに保存されます。消えると取り直しになり、短い間に何度も取り直すと、発行元の回数制限に当たるおそれがあります。- アプリは
expose:にする:Compose の同じネットワーク内では、サービス名(app)で通信できます。外にポートを出す必要はありません。
手順5:起動して確認する
docker compose up -d
docker compose ps
docker compose logs caddy
ブラウザで https://app.example.com を開き、鍵マークが付いて表示されれば完了です。http:// で開いた場合も、Caddy が自動で HTTPS に転送します。
手元の検証では、ドメインの代わりに app.localhost を使い、Caddy の内部証明書(tls internal)で同じ構成を動かしました。結果は次のとおりです。
| 確認したこと | 結果 |
|---|---|
| HTTPS でアプリが表示されるか | 表示された(ステータス 200) |
| HTTP で開いたときの動き | HTTPS へ転送された(ステータス 308) |
| アプリのポートがサーバーの外に出ていないか | docker compose ps で app は 80/tcp のみ(公開なし) |
次にやること
この形ができれば、app を差し替えるだけでほかのアプリも公開できます。