Docker ComposeでNginxを起動する方法|Ubuntu ServerでLAN内限定のWebページを公開

家庭内LANのUbuntu ServerからNginxのWebページをパソコンとスマートフォンへ配信するイメージ はじめての自宅サーバー

前回は、Ubuntu ServerへDocker EngineとDocker Composeを導入しました。今回はその続きとして、Ubuntu Server 26.04 LTSでNginx公式イメージから静的Webページを1つ起動し、家庭内LANの特定IPv4だけへ公開します。

ただ起動するだけでなく、公開先の確認、読み取り専用マウント、ヘルスチェック、サーバー再起動後の復帰、イメージ更新と切り戻しまでを一続きで確認します。Dockerが未導入なら、先にUbuntu ServerへDocker EngineとComposeを導入する方法を完了してください。

この記事はインターネット公開の手順ではありません。

特定のLAN IPv4へ割り当てても、それだけで外部から絶対に接続できないとは言い切れません。ルーターのポート転送・DMZ・UPnP、IPv6、VPN、複数NIC、ほかの中継サービスも確認します。この記事ではルーター設定を変更しません。

この記事のゴールと対象外

Nginxコンテナのポートを家庭内LAN用IPv4だけに割り当てる構成図
コンテナの80番を、サーバーの家庭内LAN用IPv4にある8080番だけへ割り当てます。全インターフェースやIPv6への公開は行いません。

完了時の状態

  • Nginx公式イメージをComposeで起動できる
  • html/index.htmlをNginxから表示できる
  • 指定したLAN IPv4の8080番だけに公開される
  • 0.0.0.0:8080[::]:8080には公開されていない
  • コンテナがhealthyになる
  • Ubuntu Server自身と家庭内LANの別PCから確認できる
  • 再起動後の自動復帰、更新、切り戻し、停止ができる

今回は扱わないこと

  • HTTPS、独自ドメイン、リバースプロキシ、アクセス認証
  • インターネット公開やルーターのポート転送
  • Nginx設定ファイルの変更、PHP、データベース
  • 複雑なUFW・nftables・iptables設定
  • Docker SwarmやKubernetes

採用するイメージタグ

この記事では、2026年8月1日時点のstable版を明示するnginx:1.30.4-alpineを使います。stable-alpineのような移動タグより変更を把握しやすく、digest固定より初心者が更新しやすいためです。Alpine版は小型で、今回の静的ファイル配信に向いています。

バージョン番号付きタグでも、同じタグの実体が将来まったく変わらない保証にはなりません。バイト単位で固定するにはdigestが必要ですが、更新も自動では入りません。この記事では「明示したタグを定期的に見直し、更新前に旧イメージを退避する」運用にします。

1. DockerとLAN IPv4を確認する

sudo docker version
sudo docker compose version
systemctl is-active docker
systemctl is-enabled docker

activeかつenabledを確認します。Dockerサービスの自動起動とコンテナの再起動ポリシーは別の仕組みです。Docker自体が起動しなければ、後で設定するrestart: unless-stoppedも働きません。

続いて、サーバーの家庭内LAN用IPv4を確認します。

ip -4 -brief address show scope global
ip route get 1.1.1.1

たとえばenp2s0 UP 192.168.1.50/24と表示され、経路のsrc192.168.1.50なら、その値を候補にできます。複数の有線LAN、Wi-Fi、VPNがある場合は、別PCが接続する家庭内LAN側を選びます。

IPの選び方が分からない、インターフェースがDOWN、再起動のたびにIPが変わる、別PCがゲストWi-Fiにいる場合は、ここで止めます。まずネットワーク構成やDHCP予約を確認してください。

2. 作業ディレクトリと.envを作る

mkdir -p ~/docker/nginx-lan/html
cd ~/docker/nginx-lan
nano .env

.envへ次を保存します。IPは必ず実機の値へ置き換えます。

LAN_IP=192.168.1.50
NGINX_IMAGE=nginx:1.30.4-alpine

プレースホルダーが残っていないことと、そのIPが実際にサーバーへ割り当てられていることを確認します。

if grep -q 'REPLACE_WITH_' .env; then
  echo "エラー: .envを実際の値へ変更してください"
  exit 1
fi

LAN_IP="$(awk -F= '$1=="LAN_IP" {print $2}' .env)"

if ip -4 -o address show scope global   | awk '{print $4}'   | cut -d/ -f1   | grep -Fxq -- "$LAN_IP"; then
  echo "OK: $LAN_IP はこのサーバーに割り当てられています"
else
  echo "エラー: $LAN_IP はこのサーバーにありません"
  exit 1
fi

3. TCP 8080番が空いているか確認する

if sudo ss -H -ltn 'sport = :8080' | grep -q .; then
  echo "エラー: TCP 8080番は使用中です"
  sudo ss -ltnp 'sport = :8080'
  exit 1
else
  echo "OK: TCP 8080番は空いています"
fi

使用中なら、既存サービスを確認せず停止したりPIDを強制終了したりしません。表示されたサービスの用途を先に確認します。

4. 公開するHTMLを作る

nano html/index.html

次の最小HTMLを保存します。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>おうちラボ Nginxテスト</title>
</head>
<body>
  <h1>Nginxが起動しました</h1>
  <p>Ubuntu Server上のDocker Composeから配信しています。</p>
</body>
</html>
chmod 755 html
chmod 644 html/index.html
ls -ld html
ls -l html/index.html

5. compose.yamlを作る

HTMLの読み取り専用マウントとNginxコンテナのヘルスチェックと自動再起動の図

ホスト側のHTMLは読み取り専用でマウントし、HTTPヘルスチェックと再起動ポリシーで稼働状態を確認します。
nano compose.yaml

次を保存します。現在のComposeではトップレベルのversion:は不要です。

services:
  web:
    image: "${NGINX_IMAGE:?NGINX_IMAGEを.envに設定してください}"
    restart: unless-stopped

    ports:
      - target: 80
        published: "8080"
        host_ip: "${LAN_IP:?LAN_IPを.envに設定してください}"
        protocol: tcp

    volumes:
      - type: bind
        source: ./html
        target: /usr/share/nginx/html
        read_only: true
        bind:
          create_host_path: false

    healthcheck:
      test:
        - CMD-SHELL
        - wget -qO- http://127.0.0.1/ >/dev/null || exit 1
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

長い形式のportshost_ipを明示し、全インターフェース公開を避けます。./htmlは読み取り専用です。create_host_path: falseにより、パスのタイプミスを空ディレクトリの自動作成で隠しません。

HTTPヘルスチェックは、コンテナ内部のNginxが実際にページを返せるか確認します。init: trueprivileged: truenetwork_mode: host、Dockerソケット共有は使いません。

6. 展開後の設定を起動前に確認する

sudo docker compose config
sudo docker compose config --environment

少なくとも、イメージがnginx:1.30.4-alpinehost_ipが実際のLAN IPv4、publishedが8080、targetが80、マウントがread_only: trueになっていることを確認します。

0.0.0.0::[::]、プレースホルダーが表示されたら起動しません。ホストIPを省略すると、公開ポートは通常すべてのIPv4インターフェースへ割り当てられます。

7. Nginxを起動する

sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=50 web

psの状態がUp ... (healthy)となり、PORTS欄が192.168.1.50:8080->80/tcpのように表示されることを確認します。0.0.0.0:8080[::]:8080なら停止して設定を見直します。

8. 4段階で公開状態を検証する

コンテナ内部の健康状態

CID="$(sudo docker compose ps -q web)"
sudo docker inspect --format '{{.State.Health.Status}}' "$CID"

期待値はhealthyです。初回がstartingなら30秒ほど待って再確認します。ただしhealthyは、別PCから接続できることまでは保証しません。

Dockerが認識する公開先

sudo docker compose port web 80
sudo docker inspect   --format '{{json .NetworkSettings.Ports}}'   "$CID"

それぞれ192.168.1.50:8080HostIpが同じLAN IPv4であることを確認します。

ホストの待ち受けとHTTP応答

sudo ss -ltnp 'sport = :8080'
curl --fail --show-error --silent   --head "http://${LAN_IP}:8080/"
curl --connect-timeout 3 "http://127.0.0.1:8080/"

LAN IPv4側はHTTP/1.1 200 OK、ループバック側は通常接続失敗になります。Dockerの実装によってssへ明確なプロセスが出ない場合もあるため、Compose設定、docker compose portdocker inspectの結果を合わせて判断します。

家庭内LANの別PC

Windows PowerShellなら、IPを実際の値へ置き換えて確認します。

Test-NetConnection 192.168.1.50 -Port 8080
(Invoke-WebRequest -Uri "http://192.168.1.50:8080/").StatusCode

TcpTestSucceeded : True200を確認し、ブラウザでも同じURLを開きます。確認用PCがゲストWi-Fiにいる、または無線LANの端末分離機能が有効なら接続できないことがあります。

「LAN内限定」を支える確認項目

今回のhost_ipはDockerホスト上の公開先を絞る設定です。次の条件もすべて確認して、はじめて意図したLAN限定運用に近づきます。

  • 指定したIPv4が本当に家庭内LAN用である
  • ルーターにTCP 8080番のポート転送がない
  • サーバーがDMZホストに指定されていない
  • UPnPで意図しない転送が作られていない
  • VPNや別NICから中継されていない
  • [::]:8080へIPv6公開されていない
  • ほかのリバースプロキシが中継していない

Dockerの公開ポートは、通常のUFWルールより先に処理される場合があります。そのため「UFWで8080番を許可していないから、Dockerも外から見えない」とは判断しません。今回はUFWを変更せず、公開先IPv4、Dockerの展開後設定、待ち受け、ルーター、別PCからの経路を組み合わせて確認します。

9. サーバー再起動後の復帰を確認する

sudo reboot

SSHで再接続後、作業ディレクトリへ戻って確認します。

cd ~/docker/nginx-lan
systemctl is-active docker
sudo docker compose ps
sudo docker inspect   --format '{{.State.Health.Status}}'   "$(sudo docker compose ps -q web)"
sudo docker compose port web 80

最後に別PCのブラウザでも再表示します。サーバーのIPが変わっていたら、先にDHCP予約などでアドレス設計を見直します。

10. 日常の確認・停止・再開

sudo docker compose config
sudo docker compose ps
sudo docker compose logs --tail=50 web
sudo docker compose stop
sudo docker compose start

stopはコンテナを残して停止し、startで再開します。設定変更後はsudo docker compose up -dで必要なコンテナを再作成します。

サービスを削除する場合は次です。

sudo docker compose down

downでコンテナとComposeネットワークは削除されますが、bind mount元のhtml/index.htmlはホスト側に残ります。この記事ではイメージやDocker全体のデータを一括削除するコマンドは使いません。

11. Nginxイメージを更新し、失敗時に戻す

Compose設定確認から起動、動作確認、更新、切り戻しまでの運用フロー図

設定確認、起動、ヘルスチェック、別PC確認を一組にします。更新後も同じ確認を行い、失敗時は保存した旧タグへ戻します。

更新前に、現在使っているイメージIDを退避タグで残します。

OLD_IMAGE_ID="$(sudo docker image inspect   nginx:1.30.4-alpine   --format '{{.Id}}')"
sudo docker image tag "$OLD_IMAGE_ID" nginx:ouchi-lab-rollback
sudo docker image inspect nginx:ouchi-lab-rollback   --format '{{.Id}}'

Nginx公式のリリースとDocker Hubの公式タグを確認し、.envNGINX_IMAGEを新しい明示バージョンへ変更します。その後、取得・再作成・検証を行います。

nano .env
sudo docker compose config
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=50 web
sudo docker compose port web 80

ヘルスチェック、LAN IPv4、別PCのHTTP応答まで再確認します。失敗したら.envを次のように変更し、退避イメージへ戻します。

NGINX_IMAGE=nginx:ouchi-lab-rollback
sudo docker compose config
sudo docker compose up -d
sudo docker compose ps
sudo docker compose port web 80

切り戻し後もhealthyと別PCの表示まで確認します。正常性を確認する前に旧イメージを削除しないことが重要です。

トラブルシューティング

bindエラーで起動できない

.envのIPが現在のサーバーに存在するか、8080番が空いているかを再確認します。DHCPでIPが変わった場合は、Composeだけでなくアドレス設計を先に直します。

unhealthyになる

sudo docker compose logs --tail=100 web
sudo docker inspect   --format '{{range .State.Health.Log}}{{.ExitCode}} {{.Output}}{{println}}{{end}}'   "$(sudo docker compose ps -q web)"

HTMLの存在と権限、Nginxの起動ログ、ヘルスチェック履歴を確認します。再起動を繰り返している場合は、原因を直す前に更新や削除へ進みません。

サーバー自身では見えるが別PCから見えない

別PCが同じ家庭内LANにいるか、ゲストWi-Fiや端末分離が有効でないか、IPを取り違えていないかを確認します。UFWだけを原因と決めつけず、Composeの公開先とルーター側も確認します。

完了チェックリスト

  • Dockerサービスがactiveかつenabled
  • 家庭内LAN側のIPv4を特定した
  • TCP 8080番が空いていることを起動前に確認した
  • docker compose configで展開後の設定を確認した
  • host_ipはLAN IPv4で、0.0.0.0::ではない
  • HTMLのbind mountは読み取り専用
  • コンテナはhealthy
  • サーバー自身と別PCの両方でHTTP 200を確認した
  • ルーターのポート転送・DMZ・UPnPとIPv6を確認した
  • 再起動後も自動復帰した
  • 更新前の旧イメージを退避し、切り戻し手順を確認した

まとめ

Docker Composeで安全に最初のWebサービスを動かす要点は、起動前に公開先を固定し、展開後の設定を読み、内部・ホスト・LANの3層で確認することです。今回は特定LAN IPv4への割り当て、読み取り専用bind mount、HTTPヘルスチェック、自動再起動、更新前の退避までを組み合わせました。

Composeのポート記法、bind mount、healthcheckはDocker Composeのservicesリファレンス、bind mountの性質はDocker公式bind mounts解説、DockerとUFWの関係はDocker公式ファイアウォール解説で確認できます。Nginxのstable版と公式イメージタグはNginx公式ダウンロードDocker HubのNginx公式タグを確認しました。公式情報の確認日は2026年8月1日です。

次は、実アプリで必要になる永続データを安全に扱うため、Dockerのボリュームとバックアップ・復元を組み立てます。

コメント

タイトルとURLをコピーしました