Docker 是一個開源的容器化平台:把「程式碼 + 執行環境 + 相依套件」打包成一個可攜帶的 image,再以彼此隔離的 container 在任何有 Docker 的機器上執行——從此告別「在我電腦上可以跑」的噩夢,部署、擴充、移除都變成一行指令的事。
如果你在 most.tw 看過本部落格的自架系列文章,會發現幾乎每篇的安裝步驟都長這樣:docker run ... 或 docker compose up -d。Caddy、n8n、Vaultwarden……這些服務之所以能「十分鐘裝好」,靠的都是 Docker。這篇就從零開始,把 Docker 的原理與實戰一次講清楚——看完你就能自己寫 Dockerfile、用 Compose 管理多服務,甚至幫自己的專案做出乾淨又可攜的部署流程。
什麼是容器?跟虛擬機器差在哪?
容器(Container)的概念一句話:把應用程式連同它需要的所有檔案、函式庫、設定,關進一個獨立的「箱子」裡執行。箱子內看到的檔案系統、網路、程序都與外界隔離,但箱子共享主機的作業系統核心(kernel)——這正是它跟虛擬機器(VM)最根本的差異:
flowchart TB
subgraph VM[虛擬機器架構]
direction TB
H1[Hypervisor<br/>虛擬化軟體層]
G1[Guest OS 完整作業系統<br/>例如 Ubuntu 22.04]
A1[App 應用程式]
H1 --- G1 --- A1
end
subgraph Docker[容器架構]
direction TB
H2[主機作業系統核心<br/>Host Kernel]
C1[Container A<br/>只有應用程式與函式庫]
C2[Container B<br/>只有應用程式與函式庫]
H2 --- C1
H2 --- C2
end| 比較 | 虛擬機器(VM) | 容器(Docker) |
|---|---|---|
| 隔離層級 | 硬體層(各自有完整 OS) | 作業系統層(共享 kernel) |
| 啟動時間 | 數十秒~數分鐘 | 毫秒~秒級 |
| 磁碟佔用 | 數 GB 起跳 | 數 MB~數百 MB |
| 資源開銷 | 高(每台 VM 都要跑整套 OS) | 低(只跑應用程式) |
| 適用情境 | 需要不同核心/完整 OS 的環境 | 微服務、CI/CD、自架服務 |
因為共享 kernel,Linux 容器只能在 Linux 主機上跑原生執行;macOS/Windows 上的 Docker Desktop 則是靠一層輕量虛擬機器(Linux VM)來跑容器。2026 年的 Docker Engine 已推進到 29.x 系列(官方文件最新到 29.7),arm64 與 amd64 的多平台鏡像支援也早就成為標準配備——樹莓派、Jetson 這類 ARM 裝置跟 x86 伺服器用同一份 image 就能跑。
安裝 Docker(Ubuntu / Debian 為例)
Linux 上安裝的是 Docker Engine——它是完全開源(Apache 2.0)、對個人與企業永遠免費的。需要付費訂閱的只有 Docker Desktop 這類圖形工具(大型企業:超過 250 名員工或年營收超過 1000 萬美元才需要買授權)。Ubuntu 建議用官方 apt 來源安裝:
# 加入官方 GPG key 與 repository
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
最後一行把 BuildKit(buildx)與 Compose v2 外掛一起裝好——現代 Docker 已經不再需要另外安裝 Python 版的 docker-compose。不想每次都用 sudo 的話,把使用者加入 docker 群組:
sudo usermod -aG docker $USER
# 重新登入後生效,用 docker run hello-world 驗證
docker run hello-world
四個核心概念:image、container、volume、network
| 概念 | 白話解釋 | 常用指令 |
|---|---|---|
| Image(映像檔) | 打包好的「安裝光碟」:唯讀的檔案系統快照,包含程式與環境 | docker pull、docker build、docker images |
| Container(容器) | Image 執行起來的「實例」:可讀寫、可啟動停止,刪掉就沒了 | docker run、docker ps、docker rm |
| Volume(磁碟卷) | 掛在容器上的持久化儲存,容器刪了資料還在 | docker volume create、-v |
| Network(網路) | 容器間溝通的虛擬網路;沒指定時容器彼此隔離 | docker network create、--network |
跑起來試試看:
# 前台跑一個 nginx,本機 8080 對應容器 80
docker run -d --name web -p 8080:80 nginx:alpine
# 看執行中的容器
docker ps
# 進到容器內部
docker exec -it web sh
# 看 log、停止、刪除
docker logs web
docker stop web && docker rm web
⚠️ 容器是無狀態的:刪掉 container,裡面寫的一切(包括資料庫檔案)都會消失。所以任何要留的資料都得放 volume——這是最多人踩的坑。
寫第一個 Dockerfile:從爛寫法到最佳實踐
Dockerfile 就是「怎麼從零把 image 蓋出來」的說明書。先看一個常見的 Python(FastAPI)範例:
# ---- 第一階段:build(編譯、裝相依套件)----
FROM python:3.12-slim AS builder
WORKDIR /app
# ① 先複製依賴清單再裝依賴:善用層快取,改程式碼不會讓依賴重裝
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# ---- 第二階段:runtime(只留執行所需,鏡像更小更安全)----
FROM python:3.12-slim
WORKDIR /app
# ② 非 root 執行:避免容器被攻破時直接拿到 root
RUN useradd --create-home appuser
COPY --from=builder /install /usr/local
COPY --chown=appuser:appuser app.py .
USER appuser
# ③ 健康檢查:讓 Docker 知道服務「真的活著」而不是程序還在
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"
EXPOSE 8000
CMD ["uvicorn", "app.py:app", "--host", "0.0.0.0", "--port", "8000"]
這個寫法示範了 2026 年的四條黃金守則:
- 層快取友善:把「很少變動的」放前面(先
COPY requirements.txt再裝依賴),「常常變動的」放後面(COPY . .)。Docker 每個指令會產生一層,層有快取——沒變的層直接沿用,改一行程式碼不必重裝整套依賴。 - 多階段構建(Multi-stage Build):第一個
FROM負責編譯與裝套件,第二個FROM只帶走成果。編譯工具、暫存檔全部留在第一階段,最終 image 乾淨又小。這是 2026 年鏡像瘦身的標準答案——第三方實測中,Go 靜態編譯搭配 Distroless 基礎鏡像可以壓到 12.8MB、.NET 9 AOT 約 14.2MB、Rust(Axum)約 21.4MB,而沒做多階段的版本動輒 100MB 以上。 - 非 root 執行:
USER appuser讓容器內程序以一般使用者身份跑,即使被攻破,攻擊者也拿不到宿主機 root。 - HEALTHCHECK:讓 Docker 與 Compose 能判斷服務是否「真正可用」,而不只是程序存在。
另外兩招進階密技:
- 建置期機密:絕對不要把 API key 寫成
ARG或ENV——它們會留在 image 歷史裡,任何人docker history都讀得到。要用 BuildKit 的機密掛載,密鑰只在那一個RUN指令執行期間短暫存在:
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --production # 建置後密鑰即消失,不留任何 layer
- 語言選型注意:Node.js/Python 這類大量呼叫底層 C 函式庫的語言,用 Debian-slim 或 Distroless 比 Alpine 穩——Alpine 的 musl libc 偶爾會跟編譯好的二進位檔打架;Go 這種純靜態編譯語言才適合放心用 Alpine。
Docker Compose:一次管好一群服務
單一容器用 docker run 就好,但真實專案幾乎都是「Web + 資料庫 + 快取 + 反向代理」的組合。Compose 讓你把整組服務用一份 YAML 描述,一條指令全部啟動。2026 年的用法重點:指令是 docker compose(空格,不是舊的 docker-compose 連字號);檔名用 compose.yaml;不要再寫 version: "3.8"——這個欄位早已廢棄,直接從 services: 開始。
下面這個範例示範 2026 年的標準架構:PostgreSQL 資料庫鎖進 internal: true 的內網(無法主動連外、杜絕資料外洩),Web 服務同時接內外網當橋樑,Caddy 只在公網邊界開 80/443:
# compose.yaml
services:
db:
image: postgres:17-alpine
# 具名 volume:資料庫資料真正落盤的地方
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD:?請設定 DB_PASSWORD}
# 健康檢查:pg_isready 就緒後才啟動 web
healthcheck:
test: ["CMD", "pg_isready", "-U", "app"]
interval: 10s
timeout: 5s
retries: 5
# 安全:丟掉所有 capabilities、檔案系統唯讀、禁止提權
cap_drop: [ALL]
read_only: true
tmpfs: [/tmp:size=64m]
networks:
- backend
web:
build: .
environment:
DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/app
depends_on:
db:
condition: service_healthy
# 開發時即時同步程式碼
# 搭配 docker compose watch 使用(sync / restart / rebuild 三種動作)
develop:
watch:
- path: ./app
action: sync
target: /app
networks:
- backend
- frontend
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data # TLS 憑證持久化
networks:
- frontend
networks:
frontend:
backend:
internal: true # 🔒 這個網路的容器無法連外網
volumes:
pgdata:
caddy_data:
啟動方式:
docker compose up -d # 背景啟動整組
docker compose ps # 看狀態
docker compose logs -f web # 追蹤某服務 log
docker compose up -d --build # 程式碼變更後重建
docker compose down # 停止並移除(volume 保留)
幾個關鍵設計直接對應實戰需求:
- 具名 volume(Named Volume):
pgdata、caddy_data由 Docker 管理,適合放「刪了容器也要留」的資料(資料庫、憑證);開發時要即時反映程式碼修改,才用 bind mount(./app:/app這種路徑對應)。 internal: true網路:資料庫這類不該上網的服務鎖在內網,就算被打穿也無法把資料外送——2026 年自架文章(例如 n8n 教學、Caddy 教學)推薦的標準隔離架構。- 健康檢查驅動啟動順序:
depends_on + service_healthy取代舊式depends_on空等,資料庫真正 ready 才拉 web。 - 環境變數:密碼用
${VAR}從.env檔讀取,別寫死在 YAML 裡。
2026 年容器安全檢查清單
| 項目 | 做法 | 為什麼 |
|---|---|---|
| 非 root | Dockerfile 結尾 USER appuser | 逃逸後拿不到 root |
| 丟棄權限 | cap_drop: [ALL] | 移除容器繼承的 Linux capabilities |
| 唯讀檔案系統 | read_only: true + tmpfs | 攻擊者無法寫入惡意檔案 |
| 禁止提權 | security_opt: [no-new-privileges:true] | 阻斷 setuid 提權路徑 |
| 資源上限 | deploy.resources.limits 或 --memory / --cpus | 單一容器吃爆整台主機的防線 |
| 保持更新 | 定期 apt upgrade Docker Engine | 容器逃逸漏洞(如 2026 年的 AF_ALG 相關 CVE)靠新版 seccomp 預設檔攔截 |
| Rootless | 桌面/開發機可跑 rootless 模式 | daemon 本身沒 root 權限,逃逸也只能拿到一般使用者 |
docker run 加上資源限制的對應寫法:docker run --memory=512m --cpus=0.5 myapp。容器不是免死金牌——它隔離的是「程式」,不是「惡意程式」,所以安全設定要做全套(業界稱為 Defense in Depth)。
常用指令速查表
| 想做的事 | 指令 |
|---|---|
| 列出所有 image/container | docker images / docker ps -a |
| 刪除沒在用的東西(清理大師) | docker system prune -a |
| 看鏡像分層歷史 | docker history <image> |
| 進容器除錯 | docker exec -it <container> sh |
| 複製檔案進出容器 | docker cp |
| 本機 build 多平台(ARM+x86) | docker buildx build --platform linux/amd64,linux/arm64 -t 帳號/專案 . --push |
| 自動產生 Dockerfile | docker init(支援 Go/Python/Node/Rust/Java 等) |
想更深入的話,可以把 Docker 接進自動化流程:搭配 GitHub Actions 完整教學 做 CI/CD、用 Tailscale 把自架服務收進內網、或學 Supabase 完整教學 看整套服務怎麼用 Compose 跑起來。
延伸閱讀
- Caddy 完整教學 2026:75K 星自動 HTTPS 網頁伺服器,從 Caddyfile、反向代理到 Docker 部署一次學會
- n8n 完整教學 2026:20 萬星開源 AI 自動化平台,Docker 安裝、工作流實戰與 AI Agent 串接一次學會
- Tailscale 完整教學 2026:36K 星 WireGuard Mesh VPN,從安裝、MagicDNS、Subnet Router 到遠端連回家中自架服務一次學會
- Vaultwarden 完整教學 2026:自架 Bitwarden 相容密碼管理器,Rust 實作、66K 星開源、記憶體只要 10MB(Docker 安裝+HTTPS 設定)
- Supabase 完整教學 2026:108K 星開源 Firebase 替代品,Postgres 資料庫、Auth 認證、Storage 儲存、Edge Functions 一次學會
- FastAPI 完整教學 2026:從安裝、路由、Pydantic v2 到依賴注入與部署,Python 最強 API 框架實戰指南
