Docker for Web Apps: A Practical Guide
Setting up Docker can feel overwhelming, but it’s really worth it for keeping my web apps consistent across environments. I’ll walk through the basics, from your first Dockerfile to running a simple PHP app.
On this page
I've found Docker incredibly useful for managing my web applications, especially when dealing with different environments for clients. It eliminates a lot of the 'works on my machine' headaches. Here’s a breakdown of how I use Docker, from the very basics.
What is Docker?
Docker is a platform for packaging, distributing, and running applications using containers. These containers are lightweight, isolated environments that contain everything your app needs to run: code, runtime, system tools, system libraries, and settings. This ensures consistency across different environments.
Your First Dockerfile
Let's start with a simple PHP application. Create a Dockerfile in your project’s root directory:
FROM php:8.3-apache
WORKDIR /var/www/html
COPY . .
EXPOSE 80
This Dockerfile does the following:
FROM php:8.3-apache: Uses the official PHP 8.3 with Apache image as the base.WORKDIR /var/www/html: Sets the working directory inside the container.COPY . .: Copies all files from your project directory into the container.EXPOSE 80: Exposes port 80, the standard HTTP port.
Building the Docker Image
Now, build the Docker image. From the directory containing your Dockerfile, run:
docker build -t my-php-app .```
`-t my-php-app` tags the image with a name, making it easier to identify later. The `.` specifies the current directory as the build context.
## Running the Container
To run the container, use the following command:
```bash
docker run -d -p 8080:80 my-php-app```
* `-d`: Runs the container in detached mode (in the background).
* `-p 8080:80`: Maps port 80 on the container to port 8080 on your host machine. Now you can access your app at `http://localhost:8080`.
## Docker Compose (for more complex apps)
For more complex applications, Docker Compose is your friend. It defines and manages multi-container Docker applications. Create a `docker-compose.yml` file:
```yaml
version: "3.9"
services:
web:
image: my-php-app
ports:
- "8080:80"
To start the application, run:
docker-compose up -d```
This command builds (if necessary) and starts the services defined in your `docker-compose.yml` file.
## Networking Between Containers
Sometimes, your web app needs to talk to a database. Docker provides networking features to make this easy. Let's say you have a MySQL database running in another container. In your `docker-compose.yml`:
```yaml
version: "3.9"
services:
web:
image: my-php-app
ports:
- "8080:80"
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: mysecretpassword
ports:
- "3306:3306"
The depends_on directive ensures the db service starts before the web service. The web container can then connect to the database using the service name db as the hostname.
Volumes for Persistent Data
By default, data inside containers is lost when the container is stopped or removed. To persist data, use Docker volumes. In your docker-compose.yml:
version: "3.9"
services:
web:
image: my-php-app
ports:
- "8080:80"
volumes:
- ./data:/var/www/html/data
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: mysecretpassword
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
This mounts the ./data directory on your host machine to /var/www/html/data inside the web container, and creates a named volume db_data for the MySQL database.
My setup
On my servers, I usually use Ubuntu 24.04 for my development and staging environments and AlmaLinux 9 for production. I don't bother with fancy orchestration tools like Kubernetes for small VPSes; Docker Compose is usually enough. A common mistake I see is forgetting to expose ports correctly, leading to applications being inaccessible. Also, remember to systemctl reload nginx or Apache after making changes to your configuration files within the container, otherwise, they won't take effect.
Troubleshooting Tips
- Image build fails: Double-check your
Dockerfilefor typos and ensure the build context contains all necessary files. I often get tripped up with case sensitivity. - Container doesn't start: Look at the container logs using
docker logs <container_id>to identify errors. - Port conflicts: Ensure the ports you're mapping are not already in use on your host machine.
- Permissions issues: When mounting volumes, make sure the user inside the container has the correct permissions to write to the mounted directory. I’ve spent too much time debugging this.
Further Learning
Docker has really simpler my workflow, and I hope this guide helps you get started too. It’s a fantastic tool for ensuring consistent deployments and simplifying application management. I find it’s a great complement to my Laravel development workflow, check out Automating Laravel Deployments with GitHub Actions for more ideas on how to automate deployments.
Related reading
Frequently asked questions
Why should I use Docker?
Docker lets me package my app and its dependencies together, ensuring it runs the same way everywhere: my laptop, staging, production. It simplifies deployment and avoids the 'works on my machine' problem.
What's the difference between a Docker image and a container?
An image is like a template, a read-only snapshot. A container is a running instance of that image. Think of it like a blueprint versus a building.
Is Docker difficult to learn?
The basics are easy to pick up. The complexity comes with more advanced configurations like networking and volumes, but you can start with simple containers and build from there.