目錄

Docker 網路教學:bridge、自訂網路與容器互連

當應用程式需要連到資料庫、快取等其他容器時,就要了解 Docker 的網路。最重要的一句話:把要互相溝通的容器放進同一個自訂網路,就能用容器名稱互相連線。

Docker 自訂網路與連接埠對應示意圖
瀏覽器透過 -p 對應的主機連接埠進入 app;app 在自訂網路中用名稱 db 連到資料庫,資料庫不需要對外開放

網路驅動程式

剛安裝好的 Docker 有三個內建網路:

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a74caef4acb9   bridge    bridge    local
b9a49b39804b   host      host      local
87a8a7fd2cd9   none      null      local
驅動程式說明
bridge預設值。容器有自己的 IP,位於一個虛擬網段中
host容器直接使用主機的網路,不做隔離(Linux 上效果最完整)
none沒有網路
overlay跨多台主機(Docker Swarm)
macvlan讓容器像實體機器一樣出現在區域網路上

預設 bridge 的限制

沒有指定網路時,容器會連到名為 bridge 的預設網路。在這個網路裡,容器之間不能用名稱連線:

$ docker run -d --name web1 nginx:1.30
b1d3fb9024c112c5990d9ac85012cd3f5e38c114ab4409a06f960c6d8cdb496e

$ docker run --rm alpine:3.23 ping -c 1 -W 2 web1
ping: bad address 'web1'

$ docker inspect --format '{{.NetworkSettings.IPAddress}}' web1

template parsing error: template: :1:18: executing "" at <.NetworkSettings.IPAddress>: map has no entry for key "IPAddress"

$ docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web1
172.18.0.2

$ docker run --rm alpine:3.23 ping -c 1 172.18.0.2
PING 172.18.0.2 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.058 ms

--- 172.18.0.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.058/0.058/0.058 ms

$ docker rm -f web1
web1
  • 用名稱 web1 找不到(bad address),用 IP 172.18.0.2 可以連到。
  • 但 IP 是 Docker 自動分配的,容器重新建立後可能改變,不能寫死在設定裡。

Docker 29 的變化:舊版可以用 {{.NetworkSettings.IPAddress}} 取得容器 IP,Docker 29 已經移除這個欄位(上面的 template parsing error)。請改用 {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}。

所以實務上幾乎都會建立自訂網路。

自訂網路:用名稱互連

docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=secret postgres:18
$ docker network create appnet
fadfeb6028cb78335fa94cfd7536265256a914cc461caae1026b6ebb21e9d7a0

$ docker run -d --name db --network appnet -e POSTGRES_PASSWORD=secret postgres:18
227033e27591e6a1c195a6d63a141aaf4486c6285477792fe3080e1d4deeb2c0

$ docker run --rm --network appnet alpine:3.23 ping -c 2 db
PING db (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.069 ms
64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.092 ms

--- db ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.069/0.080/0.092 ms

$ docker run --rm --network appnet -e PGPASSWORD=secret postgres:18 psql -h db -U postgres -c "SELECT version();"
                                                      version
--------------------------------------------------------------------------------------------------------------------
 PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
(1 row)


$ docker network inspect appnet --format "{{range .Containers}}{{.Name}} {{.IPv4Address}}{{println}}{{end}}"
db 172.19.0.2/16
  • 在 appnet 裡,db 這個名稱被 Docker 內建的 DNS 解析成資料庫容器的 IP。
  • 用 PostgreSQL 的用戶端以 -h db 連線成功,查到了資料庫版本(PostgreSQL 18.6)。
  • 資料庫容器沒有用 -p,主機和外部都連不到它,只有同一網路裡的容器可以,比較安全。

應用程式的連線設定也一樣寫容器名稱,例如:

docker run -d --name app --network appnet -p 3000:3000 \
  -e DATABASE_URL=postgres://postgres:secret@db:5432/postgres \
  myapp

注意:

  • 容器之間連線用的是容器內的連接埠(5432),和 -p 無關。
  • 只有需要讓主機或外部存取的服務(例如網站)才需要 -p。
  • 容器名稱避免使用 web、app 這類剛好也是頂級網域的單字當作連線目標;在同一網路內沒問題,但在網路外查詢時會解析到奇怪的位址,除錯時容易誤判。

一個容器加入多個網路、網路別名

$ docker network create appnet
77fadbc5633854c9e75fd9aa65325d0155ad33f5de453abac7f15f6766402bee

$ docker run -d --name app --network appnet nginx:1.30
25b4c1d92caeef5ca8765daf2f3ad6247982cacfad4914000ded8a0a455df851

$ docker network create frontend
45a9ab78fddb9f1b47a22b9ce156144286888c42eadc2b4f589986fe1f98cee0

$ docker network connect frontend app

$ docker inspect --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{$v.IPAddress}}{{println}}{{end}}' app
appnet 172.19.0.2
frontend 172.20.0.2


$ docker run -d --name search --network appnet --network-alias es nginx:1.30
43b07713e7ec032d21e37fdf690064b389f798ed26b2715e7ea18c15840647af

$ docker run --rm --network appnet alpine:3.23 getent hosts es
172.19.0.3        es  es

$ docker rm -f app search
app
search

$ docker network rm appnet frontend
appnet
frontend
  • docker network connect 把執行中的容器加入另一個網路,app 現在同時有兩個 IP。常用來做隔離:前端網路放 Nginx 與應用程式,後端網路放應用程式與資料庫,Nginx 就連不到資料庫。
  • --network-alias es 讓同一網路的其他容器也可以用 es 這個名稱連到 search。多個容器使用相同別名時,DNS 會回傳多個 IP。

管理網路

docker network create appnet
docker network ls
docker network inspect appnet               # 看有哪些容器、IP
docker network connect appnet 容器名稱       # 把執行中的容器加入網路
docker network disconnect appnet 容器名稱
docker network rm appnet
docker network prune                        # 刪除未使用的網路

連接埠對應:程式要監聽 0.0.0.0

-p 8000:8000 會讓 Docker 把送到主機 8000 的流量,轉給容器網路介面上的 8000。所以容器裡的程式必須監聽 0.0.0.0(所有介面),不能只監聽 127.0.0.1(容器自己的 localhost)。用 Python 內建的網頁伺服器實際比較:

$ docker run -d --name localonly -p 8000:8000 python:3.14-slim python -m http.server 8000 --bind 127.0.0.1
246edb96a9affefaab188ac7d5ceec2fb103b511bdac352cca6ed3a80ae6764a

$ curl -sS http://localhost:8000
curl: (52) Empty reply from server

$ docker rm -f localonly
localonly

$ docker run -d --name allif -p 8000:8000 python:3.14-slim python -m http.server 8000 --bind 0.0.0.0
438fa44edf6aec790a7e6b2bfcdb7af33c16a14c661c50b8d9564ff52844e5e6

$ curl -s http://localhost:8000 | head -n 3
<!DOCTYPE HTML>
<html lang="en">
<head>

$ docker rm -f allif
allif
  • 只監聽 127.0.0.1 時,連接埠明明有對應,curl 卻得到 Empty reply from server:流量進到了容器,但程式只在容器自己的 localhost 上等待,收不到。
  • 改成監聽 0.0.0.0 就正常了。

很多開發伺服器預設只監聽 localhost,在容器裡執行時要特別指定:

flask run --host 0.0.0.0
uvicorn main:app --host 0.0.0.0
vite --host 0.0.0.0

從容器連到主機

想讓容器存取主機上的服務(例如主機上跑的資料庫),使用特殊名稱 host.docker.internal:

$ docker run --rm alpine:3.23 getent hosts host.docker.internal

$ docker run --rm --add-host=host.docker.internal:host-gateway alpine:3.23 getent hosts host.docker.internal
172.18.0.1        host.docker.internal  host.docker.internal
  • Docker Desktop(Windows、macOS):內建支援 host.docker.internal。
  • Linux:預設沒有這個名稱(第一個指令沒有任何輸出),需要加上 --add-host=host.docker.internal:host-gateway,它會被解析成主機在 Docker 網路上的位址。

主機上的服務也必須監聽容器能連到的介面,而不只是 127.0.0.1。

host 網路與 none 網路

docker run -d --network host nginx:1.30

容器直接使用主機的網路介面,nginx 監聽 80 就是主機的 80,不需要 -p(寫了也會被忽略)。效能最好,但失去隔離,也可能和主機上的程式搶連接埠。在 Docker Desktop 上的行為與 Linux 不完全相同。

--network none 則完全沒有網路,只有本機回環介面:

$ docker run --rm --network none alpine:3.23 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever

適合處理不需要網路、或不應該連網的工作(例如處理不受信任的檔案)。

疑難排解

容器之間用 localhost 連不到

每個容器都有自己的 localhost。在 app 容器裡連 localhost:5432,連到的是 app 自己,而不是資料庫容器。請改用容器名稱,例如 db:5432。

主機連不進容器

依序檢查:

  1. docker ps 看 PORTS 欄位是否有 0.0.0.0:8080->80/tcp。
  2. 容器內的程式是否監聽 0.0.0.0(上面的 Empty reply from server 就是典型症狀)。
  3. 主機防火牆或雲端安全群組是否允許該連接埠。

容器裡無法連上網際網路

通常是 DNS 問題。可以在 /etc/docker/daemon.json(Docker Desktop 在 Settings → Docker Engine)指定 DNS,修改後重新啟動 Docker:

{ "dns": ["1.1.1.1", "8.8.8.8"] }

使用 Docker Compose 時,Compose 會自動為專案建立一個網路,服務之間直接用服務名稱就能連線。

重點回顧

  • 預設 bridge 網路不提供名稱解析,容器之間要用自訂網路。
  • 自訂網路中用容器名稱或 --network-alias 互連。
  • 程式要監聽 0.0.0.0,連接埠對應才有效。
  • 容器連主機用 host.docker.internal。

動手練習

練習 1:建立網路 lab,啟動一個名為 cache 的 redis:8-alpine 容器加入它,再用另一個容器 redis-cli -h cache ping 測試。

答案
docker network create lab
docker run -d --name cache --network lab redis:8-alpine
docker run --rm --network lab redis:8-alpine redis-cli -h cache ping

輸出 PONG。網路指令見 docker network。

常見問題

容器裡用 localhost 為什麼連不到另一個容器?

每個容器有自己的網路,localhost 指的是容器自己。要連另一個容器,請把兩者放在同一個自訂網路中,用容器名稱連線。

容器怎麼連到主機上執行的服務?

Docker Desktop 用 host.docker.internal;Linux 上加 --add-host host.docker.internal:host-gateway,見本章「從容器連到主機」。

Docker 29 怎麼取得容器的 IP?

{{.NetworkSettings.IPAddress}} 已移除,改用 docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" 容器。

更新於 2026年10月14日