Why is Docker Essential for Web Developers?
If you've ever heard or said the phrase "it works on my machine," you've exactly encountered the problem that Docker was built to solve. Docker allows you to package software along with all its dependencies — libraries, programming language versions, environment variables, and even the base operating system — into a package called an Image. You then run this image as a Container, which behaves identically in any environment, from a personal laptop to a cloud server.
For web developers, this means the end of the hassle of installing and configuring MySQL, PHP, Nginx, or Node.js on different operating systems. Instead of direct installation, you just write a docker-compose.yml file, and with a single command, the entire application infrastructure comes up. In this article, with a real example of a PHP application, you will learn the core concepts of Docker from scratch: building images with a Dockerfile, running containers, managing data with volumes, and finally lightweight orchestration with Docker Compose.
Prerequisites and Installing Docker
We assume you are familiar with basic Linux and command-line concepts. For installation, refer to the official Docker documentation. On Windows and macOS, install Docker Desktop; on Linux, install the Docker Engine package. After installation, verify the installation with the following command:
docker --version
docker run hello-world
The second command runs a test container, and if everything is correct, a welcome message will be printed.
The Concept of Image vs. Container
Think of an Image as a template or snapshot: it is read-only and includes the base operating system, application code, and settings. A Container is a running instance of that image, which has its own writable layer. You can run multiple separate containers from a single image; just like baking several identical cakes from one mold.
Building Your First Dockerfile for a PHP App
Let's build a simple PHP application with a welcome page and a MySQL connection. First, create the project structure:
my-php-app/
├── src/
│ └── index.php
├── Dockerfile
└── docker-compose.yml
We write the content of src/index.php like this:
<?php
$host = getenv('DB_HOST') ?: 'db';
$user = getenv('DB_USER') ?: 'root';
$pass = getenv('DB_PASS') ?: 'secret';
$db = getenv('DB_NAME') ?: 'app_db';
try {
$pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8mb4", $user, $pass);
$stmt = $pdo->query("SELECT 'Connected to MySQL successfully!' AS message");
echo "<h1>" . $stmt->fetch()['message'] . "</h1>";
} catch (PDOException $e) {
http_response_code(500);
echo "<h1>DB Connection Failed: </h1>" . htmlspecialchars($e->getMessage());
}
Now, let's create the Dockerfile. This file contains the instructions for building the image:
# Use the official PHP image with FPM and mysqli
FROM php:8.3-fpm
# Install necessary extensions
RUN docker-php-ext-install pdo_mysql mysqli
# Copy application code into the container
COPY src/ /var/www/html/
# Set the working directory
WORKDIR /var/www/html
# Non-root user for better security
RUN useradd -m appuser && chown -R appuser:appuser /var/www/html
USER appuser
# Default FPM port
EXPOSE 9000
Each line in the Dockerfile creates a layer in the image. Docker caches these layers; therefore, the order of instructions matters. Place instructions that change less frequently (like installing extensions) higher up so that subsequent builds take less time.
Commonly Used Dockerfile Instructions
FROM: Specifies the base image. Always use official and specific versions, notlatest.RUN: A command executed during the image build (e.g., installing a package).COPY: Copies files from the host system into the image.WORKDIR: Sets the default directory for subsequent instructions.EXPOSE: Only documents the port; it doesn't actually open it.CMDorENTRYPOINT: The default command when the container runs.
Running Containers and Managing Volumes
To build the image from the Dockerfile and run the container, execute the following commands:
# Build the image named my-php-app with tag v1
docker build -t my-php-app:v1 .
# Run the container in the background
docker run -d --name my-app -p 8080:80 my-php-app:v1
With -p 8080:80, you map port 8080 on the host system to port 80 in the container. Now open http://localhost:8080 in your browser.
Volumes; Data Storage
Containers are inherently stateless; any change in the container's filesystem is lost when the container is removed. For persistent data — such as uploaded files or databases — we use Volumes. There are two main types:
- Named Volume: Managed by Docker and stored in
/var/lib/docker/volumes. - Bind Mount: Directly connects a folder from the host system to the container. It's great for development because code changes are applied immediately.
Example of a Bind Mount for development:
docker run -d --name my-app-dev \
-p 8080:80 \
-v $(pwd)/src:/var/www/html \
my-php-app:v1
Now, any change in src/index.php is applied in the container without needing to rebuild the image. This greatly speeds up the development workflow.
docker-compose down. Always define a volume for data-driven services.
Connecting Multiple Containers with Docker Compose
A real application isn't just PHP; it also needs MySQL, Redis, or Nginx. Instead of running each container separately and connecting them manually, we use Docker Compose. We write the docker-compose.yml file like this:
services:
web:
build: .
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
environment:
DB_HOST: db
DB_USER: root
DB_PASS: secret
DB_NAME: app_db
depends_on:
- db
db:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: app_db
volumes:
- db_data:/var/lib/mysql
ports:
- "3306:3306"
volumes:
db_data:
Now, with a single command, you bring up the entire infrastructure:
docker-compose up -d
Docker creates a private network between the services; so the web container can connect to MySQL using the service name db. This is exactly the value we set as DB_HOST in the PHP code.
Commonly Used Compose Commands
docker-compose up -d: Builds and runs all services in the background.docker-compose logs -f web: Views live logs of the web service.docker-compose exec web bash: Enters the shell of a running container.docker-compose down: Stops and removes containers and the network (volumes remain).docker-compose down -v: Completely removes along with volumes — use with caution.
Common Mistakes and Troubleshooting Tips
Below, you'll see several common issues and their solutions:
1. Port is already in use
If you see the error port is already allocated, either another service is on that port or a previous container is still running. Check:
docker ps
docker stop <container_id>
2. Cannot connect to the database
Make sure DB_HOST is exactly equal to the service name in Compose (here db), not localhost. Inside the Docker network, localhost refers to the container itself, not the host.
3. Large images and slow builds
Use lightweight images like php:8.3-fpm-alpine. Also, create a .dockerignore file so that folders like vendor or node_modules are not included in the build context:
vendor/
node_modules/
.git/
*.log
4. Code changes are not applied
If you are not using a Bind Mount, you need to rebuild the image after every code change: docker-compose up -d --build. In development mode, be sure to define the volume as in the example above.
Improving Image Security and Performance
A few professional tips for production:
- Always use a specific version tag, not
latest. This ensures reproducibility. - Use a non-root user in the container (like
USER appuserin the Dockerfile). - Store sensitive environment variables like passwords in a
.envfile and don't add it to git. Useenv_filein Compose. - To reduce image size, use multi-stage builds; especially for Node.js or Go applications.
Summary and Next Steps
In this article, you learned how to bring up a PHP app along with MySQL in a few minutes using Docker. The key concepts — image, container, volume, and Docker Compose — are the foundation of any Docker project. Now you can repeat this pattern for any language and framework.
For the next step, try these exercises: add an Nginx service as a reverse proxy to Compose, or add a Redis container for caching. If you're looking for cloud environments to run your containers, ServerNet's cloud services can be a suitable option for deployment. But the most important thing is to get started right now and write your first Dockerfile.
Yorumlar 0
Henüz yorum yok — ilk siz olun!