Docker Desktop ではコンテナから host.docker.internal
を使って、ホストにアクセスすることができる。
index.js:
const express = require("express");
const app = express();
const port = 3000;
app.get("/", (req, res) => {
res.send("OK\n");
});
app.listen(port, () => {
console.log("Start");
});
$ node index.js
$ docker run -it --rm nginx curl http://host.docker.internal:3000
OK
だが、これはあくまでも Docker Desktop が提供する機能なので、それ以外のDocker環境では使用できない。また Docker Desktop でも WSL2 backend のものでは使用できないようだ。
しかし、Docker Engine 10.20.0
から --add-host="host.docker.internal:host-gateway"
オプションを指定することで、同様のことができるようになった1。このオプションは、コンテナの /etc/hosts
に docker network上でのホストのIPを host.docker.internal
として登録する。ここで、オプションとしては :host-gateway
の部分が重要で、host.docker.internal
の部分は任意の値にできる。
$ node index.js
$ docker run -it --rm --add-host="host.docker.internal:host-gateway" nginx curl http://host.docker.internal:3000
OK
$ docker run -it --rm --add-host="host:host-gateway" nginx curl http://host:3000
OK
また、--add-host
オプションは docker-compose.yml
では extra_hosts
で指定できる。
---
version: '3.8'
services:
service1:
image: image-name
extra_hosts:
- host.docker.internal:host-gateway
これを用いると、たとえば以下のようなことができるようになる。
- ローカルでポート3000にListenしている Next server をSSL化する
- k8sクラスタで起動しているMySQLに、devcontainerのコンテナからアクセスする
ローカルでポート3000にListenしている Next server をSSL化する
今日の開発環境では広く用いられている Docker だが、フロントエンド開発では Docker を使わないことが多い。これは Docker 環境を用意する必要性があまりないことと、主に Docker Desktop for Mac のホストディレクトリをマウントしたときのIO性能が低いためである(WebpackなどはIOヘビー)。
しかし、たとえばモバイル向けのSPAで写真撮影のためにカメラデバイスを使いたいといった場合、PCで起動している Next server にモバイル端末がアクセスするためにはPCの Private IP を使って(たとえば 192.168.1.2
)、http://192.168.1.2:3000
にアクセスする必要があるが、カメラデバイスにアクセスするためには localhost や fileスキームでアクセスした場合を除き、SSLで保護されている必要がある2。
ここで、SSL化するのに手っ取り早いのは mkcert でCAインストール/証明書を作成して、Docker上にnginxを立ててSSLを有効にしたserverから Next server にリバースプロキシしてやることだが、上で述べたように、フロントエンド環境をDocker上に構築したくはない。
そこで、host.docker.internal
を使って、nginx では http://host.docker.internal:3000/
にリバースプロキシしてやることで実現する。
docker-compose.yml:
version: '3.7'
services:
nginx:
image: nginx
ports:
- 0.0.0.0:80:80
- 0.0.0.0:443:443
volumes:
- type: bind
source: ./default.conf
target: /etc/nginx/conf.d/default.conf
- type: bind
source: ./certs
target: /etc/certs
extra_hosts:
- host.docker.internal:host-gateway
default.conf:
server {
listen 443 ssl default;
ssl_certificate /etc/certs/localhost+2.pem;
ssl_certificate_key /etc/certs/localhost+2-key.pem;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://host.docker.internal:3000/;
}
}
server {
listen 80;
return 301 https://$host$request_uri;
}
$ mkcert -install
$ (cd certs && mkcert localhost 127.0.0.1 192.168.1.2)
$ node index.js
$ docker-compose up
$ curl https://192.168.1.2/
OK
なお、モバイル端末にも mkcert のCAをインストールする必要があるため、たとえばiOS端末の場合、以下の手順を踏む。
- キーチェーンアクセスを起動し、mkcertでインストールしたCAを右クリックして、PEM形式で書き出す
- 書き出したPEMをAir DropなどでiOS端末に送信し、インストールする
[設定] → [一般] → [情報] → [証明書信頼設定] → ルート証明書を全面的に信頼する
と進み、インストールした証明書をONする
また、SSLは使っていないが、これに近いサンプルは sandbox/docker-host-docker-internal にある。
k8sクラスタで起動しているMySQLに、devcontainerのコンテナからアクセスする
以下のような開発開発があったとする。
- 開発開発がk8sクラスタ
- 開発対象のDockerイメージを使ってdevcontainer用のdocker-composeを立ち上げ、VS Code を使って開発
このような場合、普通はMySQLなどのDBもdevcontainer用docker-compose上に用意するか、DBアクセスはあきらめる必要があるが、host.docker.internal
と Kube Forwarder などをつかって k8s上の MySQL service をホストにポートフォワーディングすることで、devcontainer から k8s 上のMySQLにアクセスすることができる。
.devcontainer/devcontainer.json:
{
"name": "go-app",
"dockerComposeFile": [
"../docker-compose.yml"
],
"service": "app",
"remoteUser": "root",
"workspaceFolder": "/app",
"extensions": [
"golang.go"
]
}
Dockerfile:
FROM golang:1.18.2
RUN go install golang.org/x/tools/gopls@latest
WORKDIR /app
docker-compose.yml:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
command: sleep infinity
volumes:
- type: bind
source: .
target: /app
environment:
DB_HOST: host.docker.internal
extra_hosts:
- host.docker.internal:host-gateway
main.go:
import (
"fmt"
"database/sql"
"github.com/go-sql-driver/mysql"
)
func main() {
c := mysql.Config{
DBName: "dbname",
User: "root",
Passwd: "",
Addr: fmt.Sprintf("%s:3306", os.Getenv("DB_HOST")),
Net: "tcp",
}
dsn := c.FormatDSN()
db, _ := sql.Open("mysql", dsn)
var value string
db.QueryRow("SELECT 1").Scan(&value)
fmt.Println(value)
}
[host]$ skaffold dev
[host]$ devcontainer open .
[container]$ go run main.go
1
なお、今回はk8sクラスタとしたが、別のdocker-composeで起動したMySQLに接続するといったことも同様の手順で行える。
Docker Desktop WSL2 backend での注意点
Docker Desktop WSL2 backend では host.docker.internal
はWSL2を指すのではなくWindows側となるようで、コンテナからWSL2上でlistenしているポートにアクセスするためには、WSL2のlocalhostForwarding機能を有効にする必要があるようだ。
環境
- Mac:
- OS:
macOS Ventura 13.0.1
- Docker Desktop:
4.12.0
- Docker Engine:
20.10.17
- Windows:
- OS:
Windows 11 Pro 22H2
- Docker Desktop:
4.14.1
- Docker Engine:
20.10.21
- WSL:
1.0.1.0
- WSL version:
2
- WSL distribution:
Ubuntu 20.04.4 LTS
参考