# Docker Compose v5 Migration: All the Compatibility Traps I Hit (And How to Avoid Them)
Breaking changes in major version jumps are always more subtle than you think.
Background: Why I Upgraded
I was continuing development on a VPS project originally set up in 2024, using docker-compose.yml from v2.40.2. One day I ran docker compose version and saw v5.1.3 was already out (as of 2026-04-15). Thinking "minor version upgrade should be fine," I triggered a chain of failures.
Original config snippet (from v2.40.2 project):
version: "3.8"
services:
web:
build: .
ports:
- "80:80"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
This config produced 3 different errors under v5.1.3.
Trap 1: build Section Behavior Change
**Problem**: v5.0.0 removed the internal buildkit builder; all builds now delegate to Docker Bake (same as docker build command). If your docker-compose.yml relied on the internal builder's build cache, the config silently breaks.
Error:
ERROR: compose file's build section uses unsupported features: internal builder is no longer supported
Troubleshooting:
1. Confirm the error comes from docker compose build
2. Check docker buildx version (requires v0.30.0+, bundled with Docker Desktop)
3. Check if compose file uses build-specific parameters
Solution:
# v5 compatible: explicitly use docker Bake
services:
web:
build:
context: .
dockerfile: Dockerfile
builder: default # explicitly use external builder
If you don't need custom builds, use pre-built images:
services:
web:
image: Nginx 性能调优:1.30-alpine
ports:
- "80:80"
Trap 2: healthcheck CMD Syntax
**Problem**: In v5, the healthcheck test field no longer accepts array form ["CMD", ...] (documentation doesn't say clearly, but v5.1.3 triggers a warning and suggests string format).
Warning:
WARNING: The test attribute must be a list or a string. Using list is deprecated.
Troubleshooting:
1. Comment out healthcheck, see if container starts normally (yes → healthcheck is the issue)
2. Remove fields one by one to isolate the problem
Solution:
# Recommended (v5/v2 compatible)
healthcheck:
test: "curl -f http://localhost/ || exit 1"
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# If image doesn't have curl, use wget or test HTTP port directly
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
Trap 3: COMPOSE_COMPATIBILITY Variable Removed
**Problem**: v5.0.1 restored COMPOSE_COMPATIBILITY support (PR #13424), but only for v5.0.1+. If your CI environment still uses older docker compose, this variable behaves differently in v5.
**Real impact**: With COMPOSE_COMPATIBILITY=true, v5 downgrades some behavior, but version: "3.x" compose files are treated as "legacy format" in v5 — some fields get warnings (doesn't break functionality, but not recommended).
**Solution**: Uniformly use version: "3.9" or remove the version field entirely (v5 defaults to Compose Specification, highest compatibility).
# Recommended: remove version, use Compose Specification
services:
web:
image: nginx:1.30-alpine
Trap 4: Port Binding Order Difference
**Problem**: In v5, short-form port mappings ("80:80") behave differently in certain network modes. Especially with network_mode: bridge, v5 defaults to binding on 0.0.0.0 (all interfaces), while v2 some versions only bind to 127.0.0.1.
Security impact: If your service shouldn't be publicly exposed, this change causes accidental exposure.
Debugging commands:
# Check what ports the container actually listens on
docker inspect | grep -A 20 '"PortBindings"'
# Compare v2 vs v5 difference
docker run --rm nginx:1.30-alpine cat /etc/nginx/nginx.conf | grep listen
Solution:
# Explicitly specify binding IP (v5/v2 compatible)
services:
web:
ports:
- "127.0.0.1:80:80" # local access only
Upgrade Checklist
After confirming docker compose version shows v5.x, check in this order:
# 1. Check if build config relies on internal builder
docker compose config 2>&1 | grep -i "unsupported"
# 2. Check healthcheck format
docker compose config | grep -A 10 healthcheck
# 3. Check port bindings
docker compose ps
# 4. If using custom networks, verify they're working
docker network ls
docker network inspect _default
How to Avoid These Traps
1. **Don't skip minor versions**: v2 → v5 means you skipped changelogs for 3 intermediate versions. At least scan the GitHub Changelog before upgrading.
2. **Sync CI environments**: After upgrading docker compose locally, CI environments may lag behind. Pin the version explicitly in CI Dockerfile or workflow:
- name: Install Docker Compose v5
# GitHub Actions example
run: |
curl -L "https://github.com/docker/compose/releases/download/v5.1.3/docker-compose-$(uname)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
3. Keep rollback capability:
# Backup current config
cp docker-compose.yml docker-compose.yml.backup
# Rollback method if things break (Linux)
curl -L "https://github.com/docker/compose/releases/download/v2.40.2/docker-compose-$(uname)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
4. **Use docker compose watch**: v5 introduced watch — automatically rebuilds containers when you change code. More convenient than manual docker compose up --build. Config example:
services:
web:
build: .
watch:
- action: rebuild
path: .
target: /app
My Upgrade Timeline
| Time | Action | Result |
|---|---|---|
| 10:00 | Ran `docker compose upgrade` | Already on latest (v2.40.2) |
| 10:05 | Manually downloaded v5.1.3 binary | Install succeeded |
| 10:10 | `docker compose up -d` | Build error |
| 10:25 | Fixed build config | Healthcheck warning |
| 10:40 | Fixed healthcheck | Port exposure issue |
| 11:00 | All fixed | Production verified |
Total time: about 1 hour. If I'd read the changelog first: estimated 15 minutes.
---
Versions referenced (as of 2026-04-22, verify independently):
👉 Try MiniMax now: https://platform.minimaxi.com/subscribe/token-plan?code=E5yur9NOub&source=link
🔗 Related Tech Articles
Deep dive into related technical topics: