Bulut & Altyapı

Web geliştiricileri için Docker temelleri

With Docker, unify the development and deployment environment of web applications. In this guide, with a real PHP example, learn step by step how to build images, containers, and volumes.

Bulut & Altyapı

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, not latest.
  • 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.
  • CMD or ENTRYPOINT: 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.

Common Mistake: Forgetting a volume for the database. If you run MySQL without a volume, all data will be deleted with every 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 appuser in the Dockerfile).
  • Store sensitive environment variables like passwords in a .env file and don't add it to git. Use env_file in 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.

ServerNet Destek

ServerNet mühendislik ve yayın ekibi — altyapı, ağ ve web barındırma uzmanları.

İletişim
Paylaş:

Yorumlar 0

Henüz yorum yok — ilk siz olun!

Yorum bırakın