Docker for Web Developers: From Zero to Production
Containerize your applications for consistent development, testing, and deployment
Why Docker Changes Everything
Docker solves the age-old problem of "it works on my machine." By packaging your application with its exact dependencies, configuration, and runtime environment into a container, Docker ensures that your app runs identically everywhere — on your laptop, your colleague's machine, the CI server, and production.
For web developers, Docker eliminates environment setup headaches. New team members can run the entire application stack with a single command instead of spending a day installing PHP, MySQL, Redis, and a dozen other dependencies. Containers are lightweight, start in seconds, and can be thrown away and recreated at will.
Core Concepts
Understanding three concepts is enough to start being productive with Docker:
- Image — A read-only template that contains your application code, runtime, libraries, and configuration. Think of it as a snapshot of your application at a specific point.
- Container — A running instance of an image. You can start, stop, and delete containers without affecting the image. Multiple containers can run from the same image.
- Dockerfile — A text file with instructions for building an image. Each instruction creates a layer, and layers are cached for fast rebuilds.
Writing a Dockerfile
A Dockerfile for a Laravel application might look like this:
FROM php:8.3-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git curl zip unzip libpng-dev libonig-dev libxml2-dev \
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Copy dependency files first (for better caching)
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader
# Copy application code
COPY . .
RUN composer dump-autoload --optimize
EXPOSE 9000
CMD ["php-fpm"]
Notice how dependency installation is separated from code copying. This takes advantage of Docker's layer caching — if your composer.json hasn't changed, Docker reuses the cached dependency layer instead of reinstalling everything.
Docker Compose for Multi-Container Apps
Most web applications need more than one service: a web server, a database, maybe Redis for caching. Docker Compose lets you define and run multi-container applications with a single YAML file:
services:
app:
build: .
volumes:
- .:/var/www/html
depends_on:
- mysql
- redis
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: myapp
MYSQL_ROOT_PASSWORD: secret
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:alpine
volumes:
mysql_data:
Run docker compose up and your entire stack starts. Run docker compose down to stop it. Data persists in named volumes between restarts.
Development vs. Production
Your development and production Docker configurations should differ. In development, mount your source code as a volume for live reloading. In production, copy the code into the image, optimize for size, and don't include development tools. Use multi-stage builds to keep production images small — build your assets in one stage, then copy only the compiled output into a minimal runtime image.
Common Pitfalls
Don't store data inside containers — use volumes for databases and uploaded files. Keep images small by using Alpine-based base images and cleaning up in the same RUN instruction. Don't run processes as root inside containers. And always use specific version tags for base images (e.g., php:8.3-fpm) rather than latest, which can break your builds without warning.
Share this post: