在 Go 裡直接抓 Docker 容器 IP,連線 PostgreSQL
前言
開發環境裡,我們常用 Docker Compose 來啟動資料庫。像是 PostgreSQL 這種服務,我們通常會這樣做:
1ports:
2 - "5432:5432"
這樣就能用 localhost:5432
直接連資料庫,簡單又方便。
最近在思考,如果我們不想把資料庫連接埠對外暴露,想完全在 Docker network 裡面連線。
但容器的 IP 每次重啟都不一樣,我們該怎麼辦?
答案是:別手動 docker inspect
,直接在 Go 裡用 Docker API 查 IP 就好。
查完 IP,再用它去連 PostgreSQL,完全自動化,安全又乾淨。
Docker Compose 範例
最小化的 docker-compose.yaml
長這樣:
1services:
2 db:
3 image: postgres:16
4 restart: always
5 environment:
6 POSTGRES_USER: user
7 POSTGRES_PASSWORD: password
8 POSTGRES_DB: demo
9 volumes:
10 - db-data:/var/lib/postgresql/data
11
12volumes:
13 db-data:
注意:這裡沒有 ports:
,資料庫只在 Docker network 裡對外提供服務。
Go 程式實作
我們會用三個套件:
github.com/docker/docker/client
→ 查容器資訊github.com/jackc/pgx/v5
→ 連 PostgreSQLgithub.com/compose-spec/compose-go/v2/cli
→ 解析 docker-compose.yml
程式流程:
- 用 compose-go 解析
docker-compose.yml
,自動獲取專案名稱 - 根據專案名稱和服務名稱,組合出容器名稱
- 用 Docker API
ContainerInspect
拿容器資訊 - 從
NetworkSettings.Networks
讀 IP - 拼成連線字串,丟給 pgx 連資料庫
程式碼如下:
1package main
2
3import (
4 "context"
5 "fmt"
6 "log"
7 "time"
8
9 "github.com/compose-spec/compose-go/v2/cli"
10 "github.com/docker/docker/client"
11 "github.com/jackc/pgx/v5"
12)
13
14// 從 docker-compose.yml 獲取專案資訊
15func getProjectInfo(composePath string) (projectName string, serviceName string, err error) {
16 // 使用 compose-go 載入專案設定
17 options, err := cli.NewProjectOptions(
18 []string{composePath},
19 cli.WithOsEnv,
20 cli.WithDotEnv,
21 )
22 if err != nil {
23 return "", "", fmt.Errorf("無法建立專案選項: %w", err)
24 }
25
26 project, err := options.LoadProject(context.Background())
27 if err != nil {
28 return "", "", fmt.Errorf("無法載入專案: %w", err)
29 }
30
31 // 取得專案名稱
32 projectName = project.Name
33
34 // 尋找資料庫服務(這裡假設名稱是 "db")
35 for name := range project.Services {
36 if name == "db" {
37 serviceName = name
38 break
39 }
40 }
41
42 if serviceName == "" {
43 return "", "", fmt.Errorf("找不到名為 'db' 的服務")
44 }
45
46 return projectName, serviceName, nil
47}
48
49// 拿容器 IP
50func getContainerIP(ctx context.Context, containerName string) (string, error) {
51 cli, err := client.NewClientWithOpts(client.FromEnv)
52 if err != nil {
53 return "", fmt.Errorf("無法建立 Docker client: %w", err)
54 }
55 defer cli.Close()
56
57 containerJSON, err := cli.ContainerInspect(ctx, containerName)
58 if err != nil {
59 return "", fmt.Errorf("無法 inspect 容器: %w", err)
60 }
61
62 for _, network := range containerJSON.NetworkSettings.Networks {
63 return network.IPAddress, nil
64 }
65
66 return "", fmt.Errorf("找不到容器的 IP")
67}
68
69func main() {
70 ctx := context.Background()
71
72 // 自動從 compose.yml 獲取專案資訊
73 composePath := "compose.yml"
74 projectName, serviceName, err := getProjectInfo(composePath)
75 if err != nil {
76 log.Fatalf("無法獲取專案資訊: %v", err)
77 }
78
79 // Docker Compose 的容器命名規則: projectName-serviceName-1
80 containerName := fmt.Sprintf("%s-%s-1", projectName, serviceName)
81 fmt.Printf("專案名稱: %s, 服務名稱: %s, 容器名稱: %s\n",
82 projectName, serviceName, containerName)
83
84 ip, err := getContainerIP(ctx, containerName)
85 if err != nil {
86 log.Fatalf("查 IP 失敗: %v", err)
87 }
88
89 fmt.Println("找到容器 IP:", ip)
90
91 connStr := fmt.Sprintf("postgres://user:password@%s:5432/demo", ip)
92
93 conn, err := pgx.Connect(ctx, connStr)
94 if err != nil {
95 log.Fatalf("無法連線資料庫: %v", err)
96 }
97 defer conn.Close(ctx)
98
99 var now time.Time
100 err = conn.QueryRow(ctx, "SELECT now()").Scan(&now)
101 if err != nil {
102 log.Fatalf("查詢失敗: %v", err)
103 }
104
105 fmt.Println("成功連上資料庫,現在時間:", now)
106}
⚠️
常見問題與解決方案
如果你遇到類似以下的錯誤:
1github.com/docker/docker/client: github.com/docker/docker/client@v0.1.0-alpha.0: parsing go.mod:
2module declares its path as: github.com/moby/moby/client
3 but was required as: github.com/docker/docker/client
- 初始化 go.mod(如果還沒有的話):
1go mod init your-project-name
- 在
go.mod
修改依賴:
1replace github.com/docker/docker => github.com/moby/moby latest
- 下載依賴套件:
1go mod tidy
執行測試
- 先啟動 PostgreSQL 容器:
1docker compose up -d db
- 執行 Go 程式:
1go run main.go
- 會看到類似輸出:
1專案名稱: blog, 服務名稱: db, 容器名稱: blog-db-1
2找到容器 IP: 172.19.0.2
3成功連上資料庫,現在時間: 2025-08-31 23:05:00.389548 +0800 CST
說明:
blog
是自動從目錄名稱推導出的專案名稱blog-db-1
是 Docker Compose 的標準容器命名格式- 程式會自動解析
docker-compose.yml
來取得這些資訊
調整使用的 Docker Socket 路徑
環境變數
1import "os"
2
3func main() {
4 // 設定自定義 Docker socket 路徑
5 os.Setenv("DOCKER_HOST", "unix:///tmp/docker.sock")
6
7 // 其他程式碼保持不變
8 cli, err := client.NewClientWithOpts(client.FromEnv)
9 // ...
10}
在程式中指定
1cli, err := client.NewClientWithOpts(
2 client.WithHost("unix:///tmp/docker.sock"),
3)
遠端 Docker Daemon
1// TCP 連接
2cli, err := client.NewClientWithOpts(
3 client.WithHost("tcp://remote-docker-host:2376"),
4 client.WithTLSClientConfig("/path/to/ca.pem", "/path/to/cert.pem", "/path/to/key.pem"),
5)
6
7// 或使用環境變數
8os.Setenv("DOCKER_HOST", "tcp://remote-docker-host:2376")
9os.Setenv("DOCKER_TLS_VERIFY", "1")
10os.Setenv("DOCKER_CERT_PATH", "/path/to/certs")
安全考量
1# 只給 read-only 權限(如果支援)
2volumes:
3 - /var/run/docker.sock:/var/run/docker.sock:ro
4
5# 或使用 Docker-in-Docker (DinD)
6services:
7 app:
8 image: your-app
9 depends_on:
10 - docker
11 docker:
12 image: docker:dind
13 privileged: true
14 environment:
15 DOCKER_TLS_CERTDIR: ""
專案名稱推導規則:
- 如果沒有在
docker-compose.yml
中指定name:
,compose-spec/compose-go 會使用目錄名稱當專案名稱 - 也可以在 compose 檔案中明確指定:
1name: my-custom-project-name 2services: 3 db: 4 # ...
- 如果沒有在
容器命名格式:Docker Compose 使用
{projectName}-{serviceName}-{instance}
格式- 舊版本使用底線:
{projectName}_{serviceName}_{instance}
- 新版本使用破折號:
{projectName}-{serviceName}-{instance}
- 舊版本使用底線:
指定不同的 compose 檔案:
1composePath := "path/to/your/docker-compose.yml" 2projectName, serviceName, err := getProjectInfo(composePath)
多個 network 時,程式會拿第一個 network 的 IP。實務上可以指定 network 名稱。
容器 IP 只在 Docker network 裡有效,外部機器無法直接用。
小結
透過結合 compose-spec/compose-go 套件,我們實現了完全自動化的容器 IP 查詢:
- 自動解析 docker-compose.yml 檔案
- 智能推導 專案名稱和容器名稱
- 動態查詢 容器 IP 地址
- 無需 hardcode 任何容器相關資訊
這樣做比直接開 ports
麻煩一點,但更安全更靈活。
對內網環境或安全要求高的專案,這種方式非常適合。當你的專案有多個環境或經常變更容器配置時,這種自動化方法能大幅減少維護成本。