Before our microservices migration, a single production deployment took 45 minutes from merge to live. Forty-five minutes of one engineer staring at logs, everyone else blocked from merging, and a small but real chance something would break anyway.
After the migration we got it to 4 minutes. Not because we rebuilt anything, but because we went looking for the specific time sinks and removed them one by one.
Here are the six changes that mattered.
011. We stopped building everything on every deploy
Our original pipeline ran docker build on the entire monorepo on every commit. That meant rebuilding every service, even if only one had changed. The build alone took 18 minutes.
Fix: We moved to per-service Dockerfiles and only rebuilt images for services whose files had changed. A simple git diff against the last deployed commit told us which services to rebuild.
Impact: Build time dropped from 18 minutes to 2 minutes.
The trade-off is that shared code changes still rebuild multiple services, but that's a small fraction of our commits.
022. We layered our Dockerfiles properly
Most of our Dockerfiles had COPY . . as the second instruction, before RUN composer install. This is the classic Docker anti-pattern — every source change invalidated the dependency installation layer.
Fix: We reordered every Dockerfile:
```dockerfile
FROM php:8.1-fpm
# System packages (rarely change)
RUN apt-get update && apt-get install -y ...
# Composer binary
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Dependencies (change with composer.lock)
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
# Application code (changes often)
COPY . .
# Build steps
RUN php artisan config:cache && php artisan route:cache