Docker 容器化 —— Go 項目實戰指南
一、Docker 核心概念
1.1 甚麼是 Docker
Docker 是一個開源的容器化平台,它可以將應用程序及其所有依賴項打包到一個標準化的單元(容器)中,從而實現"一次構建,到處運行"。對於 Go 開發者而言,Docker 解決了以下痛點:
- 開發環境與生產環境不一致
- 依賴管理複雜(數據庫、緩存、消息隊列等中間件)
- 部署流程不標準化
- 微服務架構下多服務協同困難
1.2 三大核心概念
鏡像(Image)
鏡像是一個只讀模板,包含了運行應用所需的所有文件系統層。可以類比為"類"(Class):
- 鏡像由多層(Layer)組成,每一層代表 Dockerfile 中的一條指令
- 層是只讀的、可復用的,多個鏡像可以共享底層
- 鏡像通過
tag進行版本管理,如golang:1.22-alpine
+---------------------------+
| 應用代碼層 (COPY .) | <-- 最上層,變化最頻繁
+---------------------------+
| 依賴安裝層 (go mod) |
+---------------------------+
| 基礎工具層 (RUN apk) |
+---------------------------+
| 基礎鏡像層 (alpine) | <-- 最底層,最穩定
+---------------------------+
容器(Container)
容器是鏡像的運行實例,可以類比為"對象"(Object):
- 容器在鏡像之上添加了一個可寫層
- 每個容器都有自己獨立的文件系統、網絡、進程空間
- 容器是臨時的、無狀態的,銷毀後可寫層的數據會丟失
- 需要持久化的數據應使用數據卷(Volume)
倉庫(Registry)
倉庫是存儲和分發鏡像的服務:
- Docker Hub:官方公共倉庫,類似 GitHub
- 私有倉庫:如 Harbor、AWS ECR、阿里雲 ACR
- 鏡像通過
registry/repository:tag的格式標識,如docker.io/library/golang:1.22
二、Dockerfile 編寫(以 Go 項目為例)
2.1 基礎 Dockerfile
假設我們有一個簡單的 Go Web 服務:
// main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "OK")
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go Docker Service!")
})
log.Printf("Server starting on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
最簡單但不推薦的 Dockerfile:
# 不推薦:鏡像體積大,包含編譯工具鏈
FROM golang:1.22
WORKDIR /app
COPY . .
RUN go build -o server .
EXPOSE 8080
CMD ["./server"]
這種方式生成的鏡像通常超過 800MB,因為包含了完整的 Go 編譯工具鏈。
2.2 多階段構建(Multi-stage Build)
多階段構建是 Go 項目 Docker 化的核心技巧,可以將鏡像從 800MB+ 縮減到 10-20MB:
# ============ 第一階段:編譯 ============
FROM golang:1.22-alpine AS builder
# 設置 Go 模塊代理(國內加速)
ENV GOPROXY=https://goproxy.cn,direct
WORKDIR /app
# 先複製依賴文件,利用緩存層(詳見 2.3 節)
COPY go.mod go.sum ./
RUN go mod download
# 複製源代碼並編譯
COPY . .
# CGO_ENABLED=0 生成靜態鏈接的二進制文件
# -ldflags="-s -w" 去除調試信息,減小體積
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-o /app/server .
# ============ 第二階段:運行 ============
FROM alpine:3.19
# 安裝必要的 CA 證書(HTTPS 請求需要)和時區數據
RUN apk --no-cache add ca-certificates tzdata
# 創建非 root 用戶
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# 從 builder 階段複製編譯好的二進制文件
COPY --from=builder /app/server .
# 複製配置文件(如果有)
# COPY --from=builder /app/config ./config
# 切換到非 root 用戶
USER appuser
EXPOSE 8080
# 使用 ENTRYPOINT 而非 CMD,防止被意外覆蓋
ENTRYPOINT ["./server"]
更極致的方案 -- 使用 scratch 空鏡像:
# 編譯階段同上...
# 使用空鏡像,最終鏡像僅包含二進制文件
FROM scratch
# 從 builder 複製 CA 證書
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 從 builder 複製時區信息
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
鏡像體積對比:
| 基礎鏡像 | 最終大小 | 適用場景 |
|---|---|---|
golang:1.22 |
~850MB | 不推薦用於生產 |
alpine:3.19 |
~15MB | 推薦,支持 shell 調試 |
scratch |
~8MB | 極致精簡,無 shell |
distroless |
~12MB | Google 推薦,安全性好 |
2.3 構建緩存優化
Docker 構建時,每一層都會被緩存。只要某一層的輸入沒有變化,就會復用緩存。關鍵原則是:把變化頻率低的層放在前面,變化頻率高的層放在後面。
# 好的做法:分離依賴下載和代碼編譯
COPY go.mod go.sum ./ # 依賴文件變化少,緩存命中率高
RUN go mod download # 只有依賴變化時才重新下載
COPY . . # 代碼頻繁變化
RUN go build -o server . # 每次代碼變化都重新編譯
# 壞的做法:一次性複製所有文件
COPY . . # 任何文件變化都導致後續所有層緩存失效
RUN go mod download
RUN go build -o server .
2.4 最佳實踐總結
.dockerignore 文件
在項目根目錄創建 .dockerignore,減少構建上下文大小:
# .dockerignore
.git
.gitignore
.idea
.vscode
*.md
README*
LICENSE
Makefile
docker-compose*.yml
Dockerfile*
tmp/
vendor/
bin/
*.test
*.prof
安全性最佳實踐
# 1. 使用特定版本標籤,避免用 latest
FROM alpine:3.19 # 好
FROM alpine:latest # 壞 -- 不可復現
# 2. 以非 root 用戶運行
RUN addgroup -S app && adduser -S app -G app
USER app
# 3. 只讀文件系統(運行時指定)
# docker run --read-only --tmpfs /tmp myapp
# 4. 不要在鏡像中存儲密鑰
# 壞的做法
ENV DB_PASSWORD=secret123
# 好的做法:運行時通過環境變量或 Secret 管理工具注入
三、docker-compose 編排
3.1 為甚麼需要 docker-compose
在微服務架構中,一個 Go 項目通常依賴多個外部服務。docker-compose 允許用一個 YAML 文件定義和管理多個容器的編排。
3.2 完整示例:Go 服務 + MySQL + Redis + Consul
# docker-compose.yml
version: "3.9"
services:
# ============ Go 應用服務 ============
app:
build:
context: .
dockerfile: Dockerfile
args:
- GOPROXY=https://goproxy.cn,direct
container_name: go-app
ports:
- "8080:8080" # 主機端口:容器端口
environment:
- PORT=8080
- DB_HOST=mysql
- DB_PORT=3306
- DB_USER=root
- DB_PASSWORD=rootpassword
- DB_NAME=myapp
- REDIS_ADDR=redis:6379
- CONSUL_ADDR=consul:8500
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
consul:
condition: service_started
networks:
- app-network
restart: unless-stopped
# 資源限制
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
# ============ MySQL 數據庫 ============
mysql:
image: mysql:8.0
container_name: go-mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: myapp
MYSQL_CHARSET: utf8mb4
volumes:
- mysql-data:/var/lib/mysql # 數據持久化
- ./deploy/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql # 初始化腳本
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
restart: unless-stopped
# ============ Redis 緩存 ============
redis:
image: redis:7-alpine
container_name: go-redis
ports:
- "6379:6379"
command: redis-server --requirepass redispassword --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "redispassword", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
restart: unless-stopped
# ============ Consul 服務註冊與發現 ============
consul:
image: hashicorp/consul:1.17
container_name: go-consul
ports:
- "8500:8500" # HTTP API 和 Web UI
- "8600:8600/udp" # DNS
command: agent -server -bootstrap-expect=1 -ui -client=0.0.0.0
volumes:
- consul-data:/consul/data
networks:
- app-network
restart: unless-stopped
# ============ 數據卷定義 ============
volumes:
mysql-data:
driver: local
redis-data:
driver: local
consul-data:
driver: local
# ============ 網絡定義 ============
networks:
app-network:
driver: bridge
3.3 常用 docker-compose 命令
# 啓動所有服務(後台運行)
docker-compose up -d
# 啓動並重新構建鏡像
docker-compose up -d --build
# 查看服務狀態
docker-compose ps
# 查看某個服務的日誌
docker-compose logs -f app
# 停止所有服務
docker-compose down
# 停止並刪除數據卷(慎用,會丟失數據)
docker-compose down -v
# 只啓動某個服務及其依賴
docker-compose up -d app
# 進入某個服務的容器
docker-compose exec app sh
# 擴展服務實例數
docker-compose up -d --scale app=3
四、常用 Docker 命令速查
4.1 鏡像管理
# 構建鏡像
docker build -t myapp:v1.0 .
docker build -t myapp:v1.0 -f deploy/Dockerfile . # 指定 Dockerfile 路徑
# 查看本地鏡像
docker images
docker image ls
# 刪除鏡像
docker rmi myapp:v1.0
docker image prune # 清理懸空鏡像(dangling images)
docker image prune -a # 清理所有未使用的鏡像
# 鏡像標籤
docker tag myapp:v1.0 registry.example.com/myapp:v1.0
# 導入導出(離線場景)
docker save -o myapp.tar myapp:v1.0
docker load -i myapp.tar
# 查看鏡像構建歷史 / 層信息
docker history myapp:v1.0
docker inspect myapp:v1.0
4.2 容器管理
# 運行容器
docker run -d --name myapp -p 8080:8080 myapp:v1.0
docker run -d --name myapp \
-p 8080:8080 \
-e DB_HOST=localhost \
-v /host/data:/app/data \
--restart unless-stopped \
myapp:v1.0
# 查看運行中的容器
docker ps
docker ps -a # 包含已停止的容器
# 容器生命週期
docker start myapp
docker stop myapp
docker restart myapp
docker rm myapp # 刪除已停止的容器
docker rm -f myapp # 強制刪除(包括運行中的)
# 進入容器內部
docker exec -it myapp sh # alpine 用 sh
docker exec -it myapp bash # 帶 bash 的鏡像用 bash
# 查看日誌
docker logs myapp
docker logs -f myapp # 實時跟蹤
docker logs --tail 100 myapp # 最後100行
# 查看容器資源使用
docker stats
docker stats myapp
# 複製文件
docker cp myapp:/app/logs/app.log ./app.log # 容器到主機
docker cp ./config.yaml myapp:/app/config.yaml # 主機到容器
4.3 系統清理
# 一鍵清理:停止的容器、懸空鏡像、未使用的網絡
docker system prune
# 包含未使用的數據卷(慎用)
docker system prune --volumes
# 查看 Docker 磁盤使用情況
docker system df
五、Docker 網絡模式
5.1 bridge 模式(默認)
每個容器擁有獨立的網絡命名空間,通過虛擬網橋 docker0 進行通信:
# 創建自定義 bridge 網絡
docker network create my-network
# 將容器連接到自定義網絡
docker run -d --name app --network my-network myapp:v1.0
docker run -d --name db --network my-network mysql:8.0
# 同一網絡內的容器可以通過容器名互相訪問
# app 容器內可以用 "db:3306" 連接數據庫
自定義 bridge 網絡的優勢:
- 容器間通過名稱(DNS)互相發現
- 更好的網絡隔離
- 可以動態連接/斷開容器
5.2 host 模式
容器直接使用宿主機的網絡,沒有網絡隔離:
docker run -d --network host myapp:v1.0
# 容器中監聽 8080 端口 = 宿主機 8080 端口
# 不需要 -p 端口映射
- 優點:性能最好,沒有 NAT 開銷
- 缺點:端口衝突風險,安全性較低
- 適用場景:高性能網絡需求、網絡調試
5.3 none 模式
容器沒有網絡連接,完全隔離:
docker run -d --network none myapp:v1.0
- 適用場景:只需要進行計算、不需要網絡的任務
5.4 網絡模式對比
| 模式 | 隔離性 | 性能 | 使用場景 |
|---|---|---|---|
| bridge | 高 | 中 | 默認選擇,大部分場景 |
| host | 無 | 高 | 高性能需求 |
| none | 完全 | - | 安全敏感的計算任務 |
| overlay | 高 | 中 | 跨主機的 Swarm/K8s |
六、數據卷(Volume)與持久化
6.1 為甚麼需要數據卷
容器的文件系統是臨時的,容器被刪除後數據就會丟失。數據卷提供了持久化存儲機制。
6.2 三種掛載方式
# 1. 命名卷(Named Volume)-- 推薦用於數據持久化
docker volume create mydata
docker run -v mydata:/app/data myapp:v1.0
# 2. 綁定掛載(Bind Mount)-- 適合開發時掛載源代碼
docker run -v /host/path:/container/path myapp:v1.0
docker run -v $(pwd)/config:/app/config:ro myapp:v1.0 # 只讀掛載
# 3. tmpfs 掛載 -- 內存中的臨時文件系統
docker run --tmpfs /app/tmp myapp:v1.0
6.3 數據卷管理
# 查看所有卷
docker volume ls
# 查看卷詳情
docker volume inspect mydata
# 刪除卷
docker volume rm mydata
# 清理未使用的卷
docker volume prune
6.4 在 Go 項目中的典型用法
# docker-compose.yml 中的卷使用
services:
app:
volumes:
- ./config:/app/config:ro # 配置文件(只讀綁定)
- app-logs:/app/logs # 日誌目錄(命名卷)
- /app/tmp # 匿名卷(臨時文件)
mysql:
volumes:
- mysql-data:/var/lib/mysql # 數據庫數據(命名卷,最重要)
volumes:
app-logs:
mysql-data:
七、Docker 在微服務開發中的應用
7.1 本地開發工作流
在微服務架構下,一個 Go 服務可能依賴多個其他服務。Docker 讓本地開發變得輕鬆:
+-----------------------------------------------+
| docker-compose 編排 |
| |
| +----------+ +----------+ +----------+ |
| | 用戶服務 | | 訂單服務 | | 商品服務 | |
| | Go :8081 | | Go :8082 | | Go :8083 | |
| +----+-----+ +----+-----+ +----+-----+ |
| | | | |
| +----+--------------+--------------+----+ |
| | app-network (bridge) | |
| +----+----------+----------+-----------+ |
| | | | |
| +----+---+ +---+----+ +--+------+ |
| | MySQL | | Redis | | Consul | |
| | :3306 | | :6379 | | :8500 | |
| +--------+ +--------+ +---------+ |
+-----------------------------------------------+
7.2 熱重載開發模式
開發時可以結合 air 等工具實現代碼熱重載:
# docker-compose.dev.yml
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app # 掛載源代碼
command: air # 使用 air 熱重載
ports:
- "8080:8080"
# Dockerfile.dev
FROM golang:1.22-alpine
RUN go install github.com/air-verse/air@latest
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
# 不需要 COPY . . 因為源代碼通過 volume 掛載
CMD ["air"]
7.3 集成測試
使用 Docker 可以輕鬆搭建集成測試環境:
# 啓動測試依賴
docker-compose -f docker-compose.test.yml up -d
# 運行集成測試
go test ./... -tags=integration
# 清理
docker-compose -f docker-compose.test.yml down -v
也可以在 Go 代碼中使用 testcontainers-go 庫動態創建測試容器:
package repository_test
import (
"context"
"testing"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/mysql"
)
func TestUserRepository(t *testing.T) {
ctx := context.Background()
// 動態創建 MySQL 容器用於測試
mysqlC, err := mysql.Run(ctx,
"mysql:8.0",
mysql.WithDatabase("testdb"),
mysql.WithUsername("test"),
mysql.WithPassword("test"),
)
if err != nil {
t.Fatal(err)
}
defer mysqlC.Terminate(ctx)
// 獲取連接地址
host, _ := mysqlC.Host(ctx)
port, _ := mysqlC.MappedPort(ctx, "3306")
// 使用真實數據庫進行測試...
t.Logf("MySQL running at %s:%s", host, port.Port())
}
八、鏡像推送到 Registry
8.1 推送到 Docker Hub
# 1. 登錄
docker login
# 2. 給鏡像打標籤(格式:用戶名/倉庫名:標籤)
docker tag myapp:v1.0 username/myapp:v1.0
docker tag myapp:v1.0 username/myapp:latest
# 3. 推送
docker push username/myapp:v1.0
docker push username/myapp:latest
8.2 推送到私有倉庫
# 以阿里雲 ACR 為例
# 1. 登錄私有倉庫
docker login registry.cn-hangzhou.aliyuncs.com
# 2. 打標籤
docker tag myapp:v1.0 registry.cn-hangzhou.aliyuncs.com/myns/myapp:v1.0
# 3. 推送
docker push registry.cn-hangzhou.aliyuncs.com/myns/myapp:v1.0
8.3 搭建本地私有倉庫(Harbor)
Harbor 是企業級的 Docker 鏡像倉庫,支持權限管理、鏡像掃描等功能:
# 使用 Docker Compose 快速部署 Harbor
# 下載離線安裝包後:
./install.sh --with-chartmuseum --with-trivy
# 推送到 Harbor
docker tag myapp:v1.0 harbor.example.com/myproject/myapp:v1.0
docker push harbor.example.com/myproject/myapp:v1.0
8.4 CI/CD 中的自動構建與推送
在 GitHub Actions 中自動構建並推送鏡像:
# .github/workflows/docker.yml
name: Build and Push Docker Image
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
username/myapp:${{ github.ref_name }}
username/myapp:latest
九、Go 項目 Docker 化完整實戰流程
9.1 項目結構
myapp/
├── cmd/
│ └── server/
│ └── main.go # 入口
├── internal/
│ ├── handler/ # HTTP 處理器
│ ├── service/ # 業務邏輯
│ ├── repository/ # 數據訪問
│ └── config/ # 配置管理
├── deploy/
│ ├── mysql/
│ │ └── init.sql # 數據庫初始化
│ └── nginx/
│ └── nginx.conf # 反向代理配置
├── go.mod
├── go.sum
├── Dockerfile
├── docker-compose.yml
├── docker-compose.dev.yml
├── .dockerignore
└── Makefile
9.2 Makefile 自動化
# Makefile
APP_NAME=myapp
VERSION=$(shell git describe --tags --always --dirty)
REGISTRY=registry.example.com/myns
.PHONY: build run test docker-build docker-push deploy clean
# 本地編譯
build:
CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=$(VERSION)" \
-o bin/$(APP_NAME) ./cmd/server
# 本地運行
run:
go run ./cmd/server
# 運行測試
test:
go test ./... -v -cover
# 構建 Docker 鏡像
docker-build:
docker build -t $(APP_NAME):$(VERSION) .
docker tag $(APP_NAME):$(VERSION) $(APP_NAME):latest
# 推送鏡像
docker-push: docker-build
docker tag $(APP_NAME):$(VERSION) $(REGISTRY)/$(APP_NAME):$(VERSION)
docker push $(REGISTRY)/$(APP_NAME):$(VERSION)
# 啓動開發環境
dev:
docker-compose -f docker-compose.dev.yml up -d --build
# 啓動生產環境
deploy:
docker-compose up -d --build
# 查看日誌
logs:
docker-compose logs -f app
# 清理
clean:
docker-compose down -v
docker image prune -f
rm -rf bin/
9.3 完整的生產級 Dockerfile
# ============ 第一階段:編譯 ============
FROM golang:1.22-alpine AS builder
# 安裝必要的構建工具
RUN apk add --no-cache git ca-certificates tzdata
# 設置 Go 環境
ENV GOPROXY=https://goproxy.cn,direct
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
WORKDIR /build
# 依賴緩存層
COPY go.mod go.sum ./
RUN go mod download && go mod verify
# 編譯
COPY . .
ARG VERSION=dev
RUN go build \
-ldflags="-s -w -X main.Version=${VERSION}" \
-o /build/app \
./cmd/server
# ============ 第二階段:運行 ============
FROM alpine:3.19
LABEL maintainer="your-email@example.com"
LABEL version="${VERSION}"
LABEL description="My Go Application"
# 安裝運行時依賴
RUN apk --no-cache add ca-certificates tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
# 創建非 root 用戶和必要目錄
RUN addgroup -S app && adduser -S app -G app && \
mkdir -p /app/logs /app/config && \
chown -R app:app /app
WORKDIR /app
# 複製二進制文件
COPY --from=builder /build/app .
# 複製配置文件模板(可選)
# COPY --from=builder /build/config ./config
# 切換到非 root 用戶
USER app
# 暴露端口
EXPOSE 8080
# 健康檢查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
# 啓動
ENTRYPOINT ["./app"]
9.4 完整流程總結
# 1. 編寫代碼和 Dockerfile
# 2. 本地開發和測試
make dev # 啓動開發環境
make test # 運行測試
# 3. 構建鏡像
make docker-build # 構建生產鏡像
# 4. 本地驗證
make deploy # 啓動完整環境
make logs # 查看日誌
curl http://localhost:8080/health # 測試接口
# 5. 推送鏡像
make docker-push # 推送到倉庫
# 6. 部署到服務器
ssh your-server
docker pull registry.example.com/myns/myapp:v1.0
docker-compose up -d
# 7. 清理
make clean
十、常見問題與排查
10.1 常見問題
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 鏡像體積過大 | 未使用多階段構建 | 使用 multi-stage build |
| 構建慢 | 緩存未命中 | 先 COPY go.mod,再 COPY . |
| 容器內時區不對 | 缺少時區數據 | 安裝 tzdata,設置 TZ |
| HTTPS 請求失敗 | 缺少 CA 證書 | apk add ca-certificates |
| 無法連接其他容器 | 網絡不通 | 確保在同一 docker network |
| 數據丟失 | 未使用 volume | 重要數據用 named volume |
10.2 調試技巧
# 查看容器內部文件系統
docker exec -it myapp sh
# 查看容器網絡配置
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' myapp
# 查看容器環境變量
docker exec myapp env
# 臨時運行一個調試容器(和目標容器共享網絡)
docker run -it --rm --network container:myapp alpine sh
# 查看 Docker 構建過程(詳細輸出)
docker build --progress=plain -t myapp . 2>&1 | tee build.log
下一篇:016 - Kubernetes 入門,學習如何將 Docker 容器部署到 K8s 集群中進行生產級編排和管理。
主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/6781