把「一堆需要一起啟動的容器」用一份設定檔講清楚,讓環境可以一鍵重現

本質就是把多容器的操作流程「腳本化 + 結構化」

步驟大致上是
- 先寫一個 docker-compose.yml : 定義有哪些服務(例如 web、db)、每個服務用哪個 image、要開哪些 port、環境變數是什麼
- Compose 讀這個檔案 : 解析 services、networks、volumes 確認彼此的依賴關係
- 建立資源 : 它會自動幫你建立 network(讓容器可以互相用名稱溝通)、建立 volume(資料持久化)

- 啟動多個 container : 按照設定一次把所有服務跑起來,幫我們處理啟動順序(例如 db 先起)

- 維持這個「整組環境」
- up:全部啟動
- down:全部關掉並清理
- logs:一起看所有服務的 log

今天有一個網站需要
- 前端(React)
- 後端(Node.js)
- 資料庫(MySQL)
如果不用 Compose 要手動做三次 docker run,因為 docker run 是在「開一台機器」,所以一個個 docker run 要記
就變得很容易亂掉,換一台電腦幾乎重來一次
如果用 Compose 就只要
三個服務一起起來,而且可以直接用 service name 溝通(例如 db:3306)
Compose 適合「單機環境」,不適合大規模分散式系統
- 不會自動擴展(scale 有限)
- 沒有高可用(HA)
- 不會跨機器調度
因此如果要多台機器自動 scaling、容器調度,我們就會用 Kubernetes

Dockerfile 是在「做出一台機器」,Compose 是在「把很多台機器排好一起運作」
Dockerfile 步驟
- 從一個 base image 開始(例如 node、python)
- 安裝套件(apt-get、npm install)
- 複製程式碼進去
- 設定啟動指令(CMD)
1 2 3 4 5
| FROM node:18 WORKDIR /app COPY . . RUN npm install CMD ["npm", "start"]
|
結果會產出一個 image
Docker Compose 步驟
- 定義多個 service(web、db、redis)
- 指定每個 service 用哪個 image(可以是 Dockerfile build 出來的)
- 設定網路、port、volume
- 一次把全部 container 跑起來
1 2 3 4 5 6 7 8 9 10 11
| services: web: build: . ports: - "3000:3000"
db: image: mysql:8
redis: image: redis:latest
|
結果是啟動一整組運行中的系統

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
docker run -d -p8080:80 --name mynginx nginx
docker ps -a
docker exec -it mynginx bash
cat /etc/hosts
exit
docker run -dit alpine
docker exec -it b60ce6c54f2f8d6148346fb30eefd78c9c08a209e7581a9f810c44dbe30c922d sh
apk add curl
curl 172.17.0.2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| docker ps docker rm -f fa1 docker rm -f b60 docker run -d -p8080:80 --name myng nginx
docker run -dit --link myng:myng alpine
docker exec -it dd sh apk add curl curl myng
cat /etc/hosts exit
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
| wsl
mkdir -p my-csharp-docker-demo/nginx mkdir -p my-csharp-docker-demo/src/DemoApi cd my-csharp-docker-demo
cat > docker-compose.yml <<'EOF' services: nginx: image: nginx:alpine ports: - "8080:80" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf depends_on: - api
api: build: context: ./src/DemoApi dockerfile: Dockerfile environment: ASPNETCORE_ENVIRONMENT: Development ASPNETCORE_URLS: http://+:8080 ConnectionStrings__DefaultConnection: Server=mysql;Port=3306;Database=demo_db;User=demo_user;Password=demo_password; depends_on: - mysql
mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: demo_db MYSQL_USER: demo_user MYSQL_PASSWORD: demo_password volumes: - mysql_data:/var/lib/mysql
volumes: mysql_data: EOF
cat > nginx/nginx.conf <<'EOF' worker_processes 1;
events { worker_connections 1024; }
http { include mime.types; default_type application/octet-stream;
sendfile on;
upstream demo_api { server api:8080; }
server { listen 80; server_name localhost;
location / { proxy_pass http://demo_api; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } } EOF
cat > src/DemoApi/DemoApi.csproj <<'EOF' <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup>
</Project> EOF
cat > src/DemoApi/Program.cs <<'EOF' var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => { return Results.Ok(new { Message = "Hello from ASP.NET Core Web API!", Environment = app.Environment.EnvironmentName, Time = DateTimeOffset.Now }); });
app.MapGet("/health", () => { return Results.Ok(new { Status = "Healthy", Service = "DemoApi" }); });
app.MapGet("/config", (IConfiguration configuration) => { var connectionString = configuration.GetConnectionString("DefaultConnection");
return Results.Ok(new { DefaultConnection = connectionString }); });
app.Run(); EOF
cat > src/DemoApi/appsettings.json <<'EOF' { "ConnectionStrings": { "DefaultConnection": "" } } EOF
cat > src/DemoApi/Dockerfile <<'EOF' FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src
COPY DemoApi.csproj . RUN dotnet restore
COPY . . RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "DemoApi.dll"] EOF
find . -maxdepth 3 -type f
docker compose up -d --build
docker compose ps
curl http://localhost:8080
docker compose logs -f docker compose logs -f api
docker compose down docker compose down -v
docker compose down --rmi all
docker system prune
|

