Docker Compose 是一个强大的工具,它简化了多容器 Docker 应用的部署。我最初使用 docker run
等方式部署 Docker 容器,但为了部署 MC 服务器,并考虑到我已将闲置主机改造成 NAS,我开始探索 Docker Compose。
起初,我尝试使用 docker run
,但发现环境变量和挂载等参数修改起来非常繁琐,因此我转向了 Docker Compose。
Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。它使用 YAML 文件来配置应用程序的服务、网络和卷等。通过 Docker Compose,可以轻松管理复杂的容器部署,尤其适合需要多个服务协同工作的场景。
在使用 Docker Compose 的过程中,我形成了自己的一些理解。
Docker Compose 的理解
Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具,核心在于通过一个 YAML 文件定义多个相互关联的 Docker 容器,并将它们作为一个整体进行管理。
它可以简化配置,管理服务依赖,并且方便快捷地部署服务。
它使用 YAML 文件(通常命名为 docker-compose.yml
或 docker-compose.yaml
)来配置应用程序的服务、网络和数据卷等。
通过声明式的配置,Compose 可以轻松实现应用的快速部署和扩展。尤其适合以下场景:
- 微服务架构: 定义和管理构成应用程序的多个微服务。
- 测试环境: 在隔离的环境中快速搭建和清理测试环境。
- 持续集成/持续部署 (CI/CD): 在 CI/CD 管道中自动化部署流程。
接下来,我们将深入了解 Docker Compose 文件的结构。
Docker Compose 文件的结构
Docker Compose 文件通常命名为 docker-compose.yml
。
以下是一个包含常用和重要字段的 Docker Compose 示例文件。请注意,实际生产环境配置可能不会包含所有这些字段,因为许多字段是为特定场景或高级需求设计的。此示例旨在展示 Docker Compose 的强大功能和灵活性。
我将以 version: '3.8'
作为基础,因为它是目前推荐且功能完善的版本。
highlight1 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 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
| version: '3.8'
services: web_app: image: my-custom-web-app:latest build: context: ./web_app_source dockerfile: Dockerfile.prod ports: - "25565:25565" environment: EULA: "TRUE" volumes: - minecraft_data:/data
web_app: image: my-custom-web-app:latest
build: context: ./web_app_source dockerfile: Dockerfile.prod args: NODE_ENV: production APP_VERSION: 1.0.0 cache_from: - my-custom-web-app:build-cache labels: com.example.build-date: "2025-07-31" network: host shm_size: 2g target: production-stage no_cache: false pull: true
command: ["nginx", "-g", "daemon off;"]
entrypoint: ["/usr/local/bin/docker-entrypoint.sh"]
ports: - "80:80" - "443:443/tcp" - target: 8080 published: 8081 protocol: tcp mode: host
expose: - "9000"
volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - app_data:/var/www/html - type: bind source: ./logs target: /var/log/nginx read_only: false - type: volume source: cache_volume target: /var/cache/nginx - type: tmpfs target: /tmp/cache tmpfs: size: 100m
environment: NODE_ENV: production DATABASE_URL: postgres://user:password@db:5432/mydb
env_file: - .env.production - common.env
depends_on: - db_service - redis_cache
networks: - app_backend_network - app_frontend_network: aliases: - webapp.local - myapp.internal ipv4_address: 172.20.0.10
external_links: - existing_legacy_db:legacy_db_alias
container_name: my_web_app_container
hostname: webapp-host
domainname: example.com
user: "1000:1000"
working_dir: /app
restart: unless-stopped
healthcheck: test: ["CMD-SHELL", "curl -f http://localhost/health || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 5s disable: false
logging: driver: "json-file" options: max-size: "10m" max-file: "3"
labels: com.example.service: "web_app" com.example.tier: "frontend"
privileged: false
read_only: false
stdin_open: true
tty: true
cap_add: - NET_ADMIN - SYS_PTRACE
cap_drop: - ALL
devices: - "/dev/ttyUSB0:/dev/ttyUSB0"
dns: - 8.8.8.8 - 8.8.4.4
dns_search: - example.com - mycompany.local
tmpfs: - /run/my_app:size=64m
ulimits: nproc: 65535 nofile: soft: 20000 hard: 40000
sysctls: net.core.somaxconn: 1024 net.ipv4.ip_unprivileged_port_start: 0
security_opt: - no-new-privileges
stop_grace_period: 30s
stop_signal: SIGTERM
cgroup_parent: my_cgroup_group
cpu_shares: 512
cpu_quota: 50000
cpu_period: 100000
cpuset: "0,1"
mem_limit: 2g
mem_reservation: 1g
memswap_limit: 3g
oom_kill_disable: false
pids_limit: 200
group_add: - "100"
init: true
ipc: host
pid: host
uts: host
userns_mode: host
runtime: nvidia
shm_size: 1g
storage_opt: size: "10G"
volume_driver: local
configs: - source: my_app_config target: /etc/app/config.conf uid: "1000" gid: "1000" mode: 0444
secrets: - source: db_password_secret target: /run/secrets/db_password uid: "1000" gid: "1000" mode: 0400
profiles: - development - debug
platform: linux/amd64
deploy: mode: replicated replicas: 3 placement: constraints: - node.role == worker - node.labels.env == production preferences: - spread: node.labels.zone resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M restart_policy: condition: on-failure delay: 5s max_attempts: 3 window: 120s update_config: parallelism: 1 delay: 10s failure_action: rollback monitor: 60s max_failure_ratio: 0.1 order: start-first rollback_config: parallelism: 1 delay: 10s failure_action: pause monitor: 60s max_failure_ratio: 0.1 order: stop-first labels: com.example.swarm-service: "true" endpoint_mode: vip
networks: app_backend_network: driver: bridge driver_opts: com.docker.network.bridge.host_binding_ipv4: "0.0.0.0" attachable: true enable_ipv6: true internal: false labels: com.example.network_type: "backend" name: my_custom_backend_network ipam: driver: default config: - subnet: 172.18.0.0/16 ip_range: 172.18.0.0/24 gateway: 172.18.0.1 aux_addresses: host1: 172.18.0.2 host2: 172.18.0.3 app_frontend_network: external: true
volumes: app_data: driver: local driver_opts: type: "nfs" o: "addr=192.168.1.1,rw" device: ":/mnt/nfs_share" labels: com.example.data_type: "persistent" name: my_custom_app_data_volume cache_volume: external: true
configs: my_app_config: file: ./configs/app_settings.conf
secrets: db_password_secret: file: ./secrets/db_password.txt api_key_secret: external: true name: my_global_api_key
|
使用说明和注意事项
- 并非所有字段都必须: 这个示例包含了非常多的字段,但在实际应用中,您只需要使用其中一小部分。例如,对于一个简单的 Web 应用,您可能只需要
image
、ports
、volumes
和 environment
。
- 版本兼容性: 某些字段(如
deploy
、configs
、secrets
、profiles
、platform
以及 depends_on
的条件形式)是 Docker Compose 较新版本(V3.x 系列)才支持的。请确保您的 Docker Compose 客户端版本与文件版本兼容,以避免出现配置解析错误或功能不兼容的问题。
- 互斥字段: 某些字段是互斥的,例如
image
和 build
。您不能同时为同一个服务指定 image
和 build
,因为它们都定义了如何获取容器镜像。Compose 会根据您的配置选择一种方式来构建或拉取镜像。
- 路径: 示例中的
./web_app_source
、./nginx/conf.d
等路径都是相对于 docker-compose.yml
文件所在的目录。请确保这些路径的正确性,否则 Compose 将无法找到相关文件或目录。
- 敏感信息: 像数据库密码这样的敏感信息,强烈建议使用
secrets
字段来管理,而不是直接写在 environment
中或提交到版本控制。这有助于提高应用程序的安全性。
deploy
字段: deploy
字段主要用于 Docker Swarm 模式下的服务部署。虽然其中的 resources
限制在非 Swarm 模式下也能生效,但其他如 replicas
、placement
、update_config
等则只在 Swarm 模式下有意义。
- 注释: 我在示例中添加了大量的注释来解释每个字段的用途和可选项。在您自己的文件中,可以根据需要保留或删除这些注释。
- 实际值: 示例中的许多值(如 IP 地址、密码、路径等)都是占位符,您需要根据您的实际情况进行替换。请务必根据您的实际情况进行替换,否则示例配置可能无法正常工作。
.dockerignore
文件: 在构建镜像时,可以使用 .dockerignore
文件来排除不需要的文件和目录,从而减小镜像大小并提高构建速度。
性能优化
- 资源限制: 使用
cpu_shares
、cpu_quota
、mem_limit
等选项来限制容器的资源使用,防止资源耗尽。
- 构建缓存: 使用
build
上下文和缓存来加速镜像构建。
最佳实践和模式
- Secrets 管理: 使用
secrets
管理敏感信息,避免硬编码。
- 配置重用: 使用
extends
重用配置,减少重复。
- 健康检查: 使用
healthcheck
确保容器的健康状态。
错误处理和边缘情况
- 常见错误: 检查 Docker Compose 文件中的常见错误,例如端口冲突、卷挂载错误等。
- 日志记录: 使用日志记录来调试问题。
Minecraft 服务器示例
这是我开 Minecraft 服务器所使用的 docker-compose
文件,使用的是 itzg/minecraft-server
镜像。
原版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| version: "3.8" services: minecraft: image: itzg/minecraft-server:latest ports: - "25565:25565" environment: EULA: "TRUE" MEMORY: 4G volumes: - minecraft_data:/data
volumes: minecraft_data:
|
PoopSky2 服务器:
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
| services: mc: image: itzg/minecraft-server:latest tty: true stdin_open: true ports: - "25565:25565" environment: EULA: "TRUE" TYPE: "FABRIC" VERSION: "1.21.1" MEMORY: "8192M" MAX_PLAYERS: "10" MOTD: "欢迎来到 ShiroRikka 的 PoopSky2 服务器!" ONLINE_MODE: "false" USE_MEOWICE_FLAGS: "true" TZ: "Asia/Shanghai" DIFFICULTY: "2" FORCE_GAMEMODE: "true" SIMULATION_DISTANCE: "8" VIEW_DISTANCE: "8" SPAWN_PROTECTION: "1" ENABLE_ROLLING_LOGS: "true" LOG_TIMESTAMP: "true" ALLOW_FLIGHT: "true" SERVER_NAME: "空中厕所2" GENERIC_PACK: "/data/s.zip" volumes: - "/vol3/1000/NVMe-SSD-Data/Minecraft/PoopSky2:/data" - "/vol1/1000/Docker/Minecraft/PoopSky2/modpacks/Fabric-PoopSky2-2.3.zip:/data/s.zip"
|
你可能会觉得,我靠这个compose文件也太长了吧?其实这是因为我尽可能地将所有的字段都写出来了,实际上你只需要使用其中的一小部分就可以了。
对于一个简单的 Web 应用,您可能只需要 image
、ports
、volumes
和 environment
等字段就足够了,就像上面的MC服务器示例那样。