SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor

Docker ComposeでTailscaleを実行する方法

公式のCompose例ではホストのport 8080が公開されます。Tailscaleをsidecarにしてnetwork_modeを共有し、portを公開せずtailnet名だけで接続する方法を解説します。

Docker Compose スタックで Tailscale を実行する

Docker Compose で Tailscale を実行するには、2 つのサービスが必要です。1 つは tailnet に参加する tailscale/tailscale コンテナです。もう 1 つはアプリケーションコンテナです。このコンテナはホストでポートを公開せず、最初のコンテナのネットワーク名前空間を共有します。その結果、ノート PC から名前でアクセスでき、パブリックインターネットからはまったく到達できないサービスになります。

tailnet は、サインインしたデバイス間に Tailscale が構築するプライベートネットワークです。この用語が初めての場合は、まず Tailscale とは何か、2 台のマシンをどのように接続するかを読んでください。このガイドでは、VPS で最初の Docker Compose スタックを構築するで設定したように、Docker Engine と Compose v2 plugin がすでに動作していることを前提とします。

ベンダーの例と、開いたままになるポート

Tailscale の公式 Compose ガイドでは、これに近い構成が公開されています。

services:
  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale
    hostname: tailscale-nginx
    environment:
      - TS_AUTHKEY=tskey-auth-REPLACE-ME
      - TS_STATE_DIR=/var/lib/tailscale
    volumes:
      - ./tailscale-state:/var/lib/tailscale
    cap_add:
      - net_admin
      - net_raw
    restart: unless-stopped

  nginx:
    image: nginx:latest
    container_name: nginx_server
    ports:
      - "8080:80"
    depends_on:
      - tailscale
    restart: unless-stopped

tailscale サービスの各行は正しい内容です。TS_AUTHKEY はノードを認証します。TS_STATE_DIR は tailscaled に状態の書き込み先を指定し、bind mount によってその状態をディスク上に保持します。問題は 2 つ目のサービスです。

この 2 つのコンテナは、デフォルトの Compose bridge network 上にあり、それぞれ固有のアドレスを持ちます。これは、Compose network がサービス名でコンテナを接続する仕組みで説明した通常の動作です。tailscale コンテナは自身のために tailnet に参加しているだけで、nginx コンテナへ何も転送しません。そのため、tailnet から nginx への経路はまったくなく、VPS の外部から入る唯一の方法はホストの port 8080 です。

公開ポートは、先頭にアドレスを指定するか、network 上で別のデフォルト bind アドレスを設定しない限り、0.0.0.0 に bind されます。そのため VPS では、そのポートがパブリック IP で応答します。アプリは名前上は tailnet 上にありますが、実際にはインターネット上に公開されています。ホストの firewall でも防げません。Docker は ufw の chain より先に独自の forwarding rule を挿入するためです。これが、ufw の deny rule で公開された Docker port を閉じられない理由で説明した落とし穴です。

もう 1 つ、明記しておくべき点があります。この例では net_admin と net_raw を付与していますが、/dev/net/tun は map していません。TS_USERSPACE のデフォルト値は true です。そのためコンテナは userspace network stack を使用し、この 2 つの capability は機能しません。

サイドカー: 1 つの namespace、公開ポートなし

network_mode: service:tailscale を使い、アプリケーションを tailscale コンテナの network namespace 内で実行します。これにより、2 つのプロセスは別々のコンテナで動作していても、同じ loopback と同じ tailnet アドレスを認識します。

services:
  tailscale:
    image: tailscale/tailscale:v1.102.5
    container_name: ts-nginx
    hostname: nginx-demo
    environment:
      - TS_AUTHKEY=${TS_AUTHKEY}
      - TS_HOSTNAME=nginx-demo
      - TS_STATE_DIR=/var/lib/tailscale
    volumes:
      - ./ts-state:/var/lib/tailscale
    restart: unless-stopped

  nginx:
    image: nginx:1.30.4-alpine
    network_mode: service:tailscale
    depends_on:
      - tailscale
    restart: unless-stopped

鍵は compose ファイルの横にある .env ファイルに保存します。YAML には記述しないため、commit するファイルに Secret は含まれません。1 行で TS_AUTHKEY=tskey-auth-... と記述します。このパターンの詳細は commit した compose ファイルから Secret を除外する を参照してください。

起動して、両方の動作を確認します。

docker compose up -d
docker compose exec tailscale tailscale status
docker compose logs --tail 20 tailscale

tailscale status を実行すると、この node の 100.x アドレスを含む行と、tailnet 上の他のマシンが表示されます。同じ tailnet にサインインしている laptop から curl http://nginx-demo/ を実行すると、nginx の welcome page が返ります。VPS 自身で sudo ss -lntp | grep 8080 を実行しても何も返りません。ポートを公開していないためです。

8080 ではなく 80 を使う理由は、userspace mode では tailscaled が受信した tunnel 接続を localhost の同じポートへ転送するためです。共有 namespace 内で nginx は 80 で待ち受けるため、tailnet からも 80 で到達できます。アプリケーションが待ち受けるポートを変更すると、tailnet 側のポートも変わります。この namespace 共有の手法は Tailscale 固有ではありません。ホストや stack の他のサービスへの接続方法についても、近隣コンテナの network を管理する Gluetun コンテナ と同じ問題が生じます。

コンテナにはどの auth key が必要ですか?

2 回目の起動時の動作は key の種類で決まるため、デプロイする前に選択してください。admin console の Keys ページで生成します。ダイアログに表示されるのは 1 回だけです。

  • One-off key は単一のデバイスを認証します。state directory を含めずに stack を再作成すると、起動できません。
  • Reusable key は任意の数のデバイスを認証します。通常、Compose stack にはこの種類を使用します。
  • Ephemeral key はノードを自動削除の対象として登録します。Tailscale は、ephemeral device を最後のアクティビティから 30 to 60 分後に削除します。
  • Pre-approved key は手動のデバイス承認を省略します。これは、tailnet で device approval が有効な場合にだけ関係します。
  • Tagged key は、認証時に tag:container などの ACL tag を適用します。デバイスは個人に属さなくなり、key expiry はデフォルトで無効になります。

最後の項目が運用上重要です。node key はデフォルトで 180 日後に期限切れになります。期限切れになった node は、ユーザーが再度サインインするか、admin が key expiry を無効にするまで tailnet から外れます。Tagged key を使うとこの期限切れを防げるため、サーバーやコンテナには tag を使用します。

auth key の期限切れは別のイベントであり、両者は混同されがちです。auth key の有効期間は 1 to 90 日で、デフォルトは 90 日です。key が有効期限に達しても、すでに認証済みのデバイスは切断されません。新しいデバイスの追加だけができなくなります。長期間稼働させるサービスには、ephemeral ではない reusable tagged key を使用してください。preview environment のように stack を頻繁に破棄する場合は、ephemeral key を使うと、手動で削除しなくても admin console を整理された状態に保てます。

コンテナが新しいマシンとして戻ってくるのはなぜですか?

tailscaledが、再起動後も保持されない場所に状態を保存していたためです。TS_STATE_DIRが未設定の場合、イメージは状態をメモリにしか保持しません。TS_STATE_DIRを設定してもマウントしていない場合、状態はコンテナの書き込み可能レイヤーに保存され、docker compose downがコンテナを削除します。

ノードの識別情報は、その状態ディレクトリに保存されます。これを永続化すると、コンテナは再起動後も名前と100.xアドレスを維持し、保存済みの serve 設定も保持します。失うと、次回の起動は初回起動になります。コンテナは同じ key で再認証し、管理コンソールには2台目のマシンが追加されます。どちらもホスト名nginx-demoを名乗るため、MagicDNS は新しい方に番号付きサフィックスを付けます。保存していたリンクはすべて停止したノードを指すことになります。

2つの条件を満たす必要があります。TS_STATE_DIR=/var/lib/tailscaleを設定する必要があります。Kubernetes 外ではデフォルト値がないためです。また、そのパスをマウントする必要があります。上記の bind mount または named volume を使用できます。この選択については、bind mount と named volume の比較で説明しています。片方だけを設定するのが、よくある間違いです。TS_STATE_DIRのないマウントでは、再起動のたびに識別情報が失われます。マウントのないTS_STATE_DIRでは、静かに失敗します。最初のdownまでは、スタックが正常に動作するためです。

推測せずに確認してください。

docker compose down
ls -l ./ts-state
docker compose up -d
docker compose exec tailscale tailscale status

./ts-stateには、2回目のupの前にtailscaled.stateがすでに含まれているはずです。また、ノードは以前と同じアドレスで戻るはずです。アドレスが変わっている場合、マウントが機能していません。

イメージを固定し、使用したタグを記録する

tailscale/tailscale:latest は最新の安定ビルドを指します。6 か月後に docker compose pull を実行すると別の tailscaled が取得され、次回の docker compose up -d では、選択していないコードでコンテナが再作成されます。通常の再起動では古いイメージが使われるため、変更が反映されるのは pull した時点ではなく、次にコンテナを再作成した時点です。上記の構成では、2026 年 10 月 10 日時点で Docker Hub の stable と latest タグが指していたイメージである v1.102.5 を固定しています。Tailscale の変更履歴には、2026 年 10 月 7 日にリリースされた 1.104.1 クライアントがすでに掲載されています。固定先を変更する前に、Docker Hub で対応するイメージタグを確認してください。Docker Hub にはパッチ系列用の v1.102 と、サーバーでは使用しない unstable タグも公開されています。

意図したタイミングでアップグレードしてください。

docker compose pull tailscale
docker compose up -d
docker compose exec tailscale tailscale version

タグを編集して up -d を実行すると、コンテナが再作成されます。これはコンテナの再起動とは異なります。バージョン変更が反映されない場合に調査する前に、restart、up、rebuild の違いを確認してください。

ユーザー空間ネットワークとそのコスト

TS_USERSPACE のデフォルト値は true です。コンテナはユーザー空間で TCP/IP スタックを実行し、/dev/net/tun には一切アクセスしません。そのため、コンテナに TUN デバイスを渡さないホストでも動作します。受信接続は引き続き利用できます。受信したトンネル接続が localhost 上の同じポートへ転送されるためです。上記の sidecar にデバイスも capabilities も必要ない理由はこれだけです。

コストが発生するのは送信側です。ユーザー空間モードでは、アプリケーションは別の tailnet ノードへ単純にソケットを開けません。代わりに tailscaled は SOCKS5 プロキシと HTTP プロキシを提供します。そのため、tailscale サービスに TS_SOCKS5_SERVER=localhost:1055 を設定し、アプリに ALL_PROXY=socks5://localhost:1055 を設定する必要があります。また、アプリがその設定を適用しなければなりません。プロキシ環境変数を無視するアプリケーションは tailnet に到達できません。

このスタックには、把握しておくべき制限もあります。転送できるのは TCP と UDP だけです。そのため、SCTP などの他の IP プロトコルは通過しません。ICMP は ping に限定され、daemon が再構成するため、見かけ上の遅延が少し増えます。接続はノードでいったん終端され、対象へ再度発信されます。そのため、エンドツーエンド接続ではありません。version 1.20 以降では、ユーザー空間ノードから exit node、または他のノードが広告する subnet route に到達できます。ただし、これらはプロキシ経由に限られます。また、ユーザー空間ノード自身がそれらを広告することもできます。

送信トラフィックを透過的に処理する必要がある場合は、tailscale サービスに 3 つの項目を追加して kernel networking に切り替えます。

    environment:
      - TS_USERSPACE=false
    devices:
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin

まず test -c /dev/net/tun && echo ok で、ホストが TUN デバイスを提供できることを確認します。KVM ではデバイスが存在します。ホストの kernel を共有するコンテナ仮想化では、デバイスがない場合があります。その場合、選択肢は userspace だけです。コンテナを プライベート範囲を広告する subnet router または 他のデバイス向けの exit node として使用する場合は、コンテナに TUN デバイスを渡してください。これらの役割は、userspace の影響を特に受けやすいためです。

サービスへのアクセス: Serve、または単純な MagicDNS 名

最も簡単なのは MagicDNS 名を使う方法です。tailnet 上の任意のデバイスから http://nginx-demo/ にアクセスでき、完全修飾名の http://nginx-demo.your-tailnet.ts.net/ も使用できます。ここでの HTTP は通信経路上で平文ではありません。WireGuard が 2 つのノード間の通信を暗号化するためです。ただし、coordination server が確認できる情報と確認できない情報を読むと、この保証がどこまで及ぶかを確認できます。証明書がないため、ブラウザーはこのオリジンを安全でないと表示します。また、secure context を要求する Web 機能は実行を拒否します。

もう 1 つの方法は、コンテナ内で Tailscale Serve を実行することです。

docker compose exec tailscale tailscale serve --bg localhost:80
docker compose exec tailscale tailscale serve status

これにより、Tailscale が発行する証明書を使ってアプリを https://nginx-demo.your-tailnet.ts.net で公開できます。MagicDNS と HTTPS 証明書の両方を管理コンソールの DNS ページで有効にする必要があります。そうしないと、証明書に設定する名前がありません。--bg は設定を、永続化した tailscaled の state に書き込みます。そのため、コンテナとともに設定が復元されます。tailscale serve reset は設定を削除します。シェルコマンドではなくリポジトリ内で設定を管理したい場合は、TS_SERVE_CONFIG で JSON ファイルを指定できます。Serve は tailnet 内にとどまります。同じサービスをパブリックインターネットに公開する別のコマンドが Funnel です。どちらかを入力する前に、Serve と Funnel の違いを確認してください。

障害の種類と表示されるメッセージ

Error response from daemon: conflicting options: port publishing and the container type network mode. sidecar サービスに ports: ブロックを残しています。ポートを公開できるのは namespace を所有するコンテナだけです。tailnet 専用のアプリはポートを公開しません。ブロックを削除してください。

アプリコンテナは実行中ですが、何も到達しません。 tailscale サービスだけを単独で再作成しています。サービスとともに、そのサービスが所有していた namespace も破棄されました。アプリは、すでに存在しない対象に接続されています。docker compose up -d --force-recreate を使って、両方を同時に再作成してください。

管理コンソールにノードが表示されません。 docker compose logs tailscale を確認してください。拒否された key はそこに記録されます。すでに使用した one-off key と、有効期限が切れた key は、ノードが tailnet に到達する前に接続を停止させます。

ノードは起動しており、tailscale status も正しいように見えますが、curl http://nginx-demo/ がハングします。 アプリが想定した場所で待ち受けていません。共有 namespace の内部から docker compose exec nginx wget -qO- http://localhost/ で接続を確認してください。ここでも失敗する場合、問題は Tailscale にではなくアプリにあります。成功する場合、アプリはすべてのインターフェースではなく、1 つのインターフェースにだけ bind されています。

stack を停止してから 1 時間後に、マシンがコンソールから消えました。 key は ephemeral でした。最後のアクティビティから 30〜60 分後に削除されます。これは想定された動作です。

version 1.78 以降では、イメージから認証不要の /healthz endpoint を公開できます。TS_ENABLE_HEALTH_CHECK=true を設定すると、TS_LOCAL_ADDR_PORT で待ち受けます。デフォルト値は [::]:9002 です。Compose の healthcheck からこの endpoint を確認するように設定すると、認証に失敗したノードを正常に見えるまま放置せず、unhealthy として報告できます。Tailscale の coordination server に依存したくない場合は、同じ compose file から TS_EXTRA_ARGS=--login-server=https://headscale.example.com を通じて独自の control plane に接続できます。これは、独自の control server として Headscale を実行するための出発点になります。

FAQ

tailnet 上の他のデバイスからアプリケーションコンテナに接続できないのはなぜですか?

tailscale コンテナ自体だけが tailnet に参加しているためです。アプリケーションが独自のアドレスを持つデフォルトの Compose bridge network で実行されている場合、tailscale node はそのアプリケーションへ何も転送しません。ホスト外部から入る方法は、公開されたホストポートだけです。アプリケーションに network_mode: service:tailscale を指定して tailscale コンテナの network namespace を共有させ、その ports: ブロックを削除します。これで、アプリケーションが待ち受けるポートで tailnet から接続できるようになります。

Docker Compose で Tailscale を実行するには /dev/net/tun が必要ですか?

受信アクセスには必要ありません。TS_USERSPACE のデフォルト値は true です。このモードでは tailscaled が独自の network stack を実行し、受信した tunnel 接続を localhost の同じポートへ転送します。そのため、デバイスや追加の capabilities がなくても sidecar を使用できます。コンテナから tailnet へ透過的に outbound 接続を開始する場合、または subnet router や exit node として動作させる場合は、/dev/net/tun、TS_USERSPACE=false、net_admin が必要です。

Compose stack では ephemeral と reusable のどちらの auth key を使用すべきですか?

長期間稼働させる用途では、tag を付けた reusable key を使用します。ephemeral にはしません。tag 付きの key で認証した device では、デフォルトで node key expiry が無効になるため、誰かが再認証するまで 180 days 後にコンテナが tailnet から離脱することはありません。すでに認証済みの node に tag を追加しても、再認証するまで expiry は変わりません。preview environment など、頻繁に破棄する stack だけ ephemeral を選択します。Tailscale は、ephemeral device を最後のアクティビティから 30 to 60 minutes 後に削除するため、admin console を整理された状態に保てます。

コンテナを再起動するたびに新しい machine として表示されるのはなぜですか?

state directory が永続化されていないため、tailscaled が identity のない状態で起動し、まったく新しい node として認証しています。TS_STATE_DIR=/var/lib/tailscale を設定します。Kubernetes 外部ではデフォルト値がありません。そのパスを bind mount または named volume にマウントします。TS_STATE_DIR がない場合、state はメモリ上に保存され、再起動のたびに失われます。mount なしで設定すると、最初の docker compose down までは正常に見えます。stack を停止している間に、マウント先のディレクトリに tailscaled.state が存在することを確認します。