- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Docker for PHP Developers: Images, Containers and Volumes Finally Explained
About Post
A lot of PHP developers have a slightly awkward relationship with Docker. They use it every day, because the project came with a docker-compose.yml, but if you ask them what the difference between an image and a container is, or where their database data actually lives, the answer gets vague.
That's fine until something breaks. Then "I'll just delete everything and rebuild" wipes the local database, and the vagueness becomes expensive.
The good news: if you understand PHP classes and objects, you already understand Docker. Let me show you.
The mental model in three lines
- An image is a class. A read-only template: an operating system base, PHP, extensions, your code, all frozen at build time.
- A container is an object. A running instance of an image. You can run many containers from one image, each with its own state.
- A volume is the database. Well, the place where data that must survive goes. Containers are disposable; volumes aren't.
And the Dockerfile is the class definition: the instructions to build the image. Docker Compose is the bootstrap file that says which objects to create and how they talk to each other.
The most important consequence of this model: anything you change inside a running container, outside a volume, is gone when the container is removed. Install a PHP extension by hand inside a container and it disappears on the next rebuild, exactly like setting a property on an object and then creating a new one.
A Dockerfile for a Laravel app
Here's a simplified Dockerfile using the official PHP image with Apache. It's a good starting point for local development and a base you can harden for production:
FROM php:8.4-apache
RUN apt-get update && apt-get install -y git unzip libzip-dev \
&& docker-php-ext-install pdo_mysql zip \
&& a2enmod rewrite
# Serve Laravel's public/ folder instead of the default web root
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader
COPY . .
RUN composer dump-autoload --optimize \
&& chown -R www-data:www-data storage bootstrap/cache
Two things worth noticing:
- Layer order matters. Each instruction creates a cached layer. By copying
composer.jsonandcomposer.lockfirst and installing dependencies before copying the rest of the code, Docker can reuse the slowcomposer installlayer whenever only your code changes. Copy everything first and every tiny edit reinstalls all your packages. - Composer comes from its own image.
COPY --from=composer:2grabs the binary from the official Composer image, no download script needed.
Also add a .dockerignore with at least vendor, node_modules, .env and .git. Otherwise you'll copy your local secrets and a few hundred megabytes of junk into the image.
Compose: the app and MySQL together
services:
app:
build: .
ports:
- "8000:80"
environment:
DB_CONNECTION: mysql
DB_HOST: mysql
DB_DATABASE: app
DB_USERNAME: root
DB_PASSWORD: secret
depends_on:
- mysql
mysql:
image: mysql:8.4
environment:
MYSQL_DATABASE: app
MYSQL_ROOT_PASSWORD: secret
volumes:
- dbdata:/var/lib/mysql
volumes:
dbdata:
Run docker compose up -d --build, then docker compose exec app php artisan migrate, and your app is on http://localhost:8000. (Using root and a plain-text password is fine for a local setup, never for anything shared.)
Notice DB_HOST: mysql. Inside the Compose network, each service is reachable by its service name. 127.0.0.1 inside the app container means the app container itself, where there's no MySQL. This one line is behind a huge share of "connection refused" questions.
Also, depends_on only waits for the MySQL container to start, not for MySQL to be ready to accept connections. If your app runs migrations on boot, add a healthcheck or a retry.
Volumes: where your data actually lives
There are two kinds you'll use constantly, and mixing them up causes most of the confusion:
| Named volume | Bind mount | |
|---|---|---|
| Looks like | dbdata:/var/lib/mysql | ./:/var/www/html |
| Managed by | Docker | You (it's a folder on your machine) |
| Best for | Database files, anything to persist | Live-editing code during development |
Survives docker compose down | Yes | Yes, it's your folder |
Survives down -v | No, it's deleted | Yes |
That last row is the one that hurts. docker compose down -v removes named volumes, which means your local database is gone. Know what -v does before you type it.
The mistakes everyone makes first
- The bind mount that hides
vendor. For development you'll probably mount your code with- .:/var/www/htmlso edits show up instantly. But that mount covers everything at that path, including thevendorfolder installed during the build. If your local folder has novendor, the app suddenly can't find its autoloader. Runcomposer installinside the container after mounting, or keep the mount and the build path separate. - Treating containers like servers. SSH-ing in and installing things by hand. Put it in the Dockerfile or it doesn't exist.
- Permission errors on
storage/. Apache runs aswww-data, and with bind mounts on Linux, files belong to your host user. The classic "laravel.log could not be opened" error. Fix ownership, or run the container with a matching user ID. - Baking secrets into the image. Copying
.envinto an image means anyone with the image has your keys. Pass config as environment variables at runtime. - Using
latesttags.php:latesttoday andphp:latestin six months are different PHP versions. Pin versions likephp:8.4-apacheandmysql:8.4. - Forgetting extensions. The official PHP image is minimal. If you need
gd,intlorbcmath, add them withdocker-php-ext-install(some need system libraries installed first).
Remember: image = class, container = object, volume = the only place data survives. If something matters, it's either in the Dockerfile or in a volume. Everything else is temporary.
Do you even need to write this yourself?
For local Laravel development, Laravel Sail gives you a ready-made Compose setup with PHP, MySQL, Redis and more. It's a great way to start. But understanding the pieces underneath is what lets you fix it when it breaks, build a production image, or run the same setup in CI so your tests run on the same stack as production.
That last part is the real prize. Docker's biggest gift isn't convenience. It's that "works on my machine" and "works on the server" finally mean the same thing.
What was the Docker concept that took you longest to really understand? My bet is that volumes top the list.

Be first to comment it...