Docker Nginx PHP MySQL: Your Ultimate Guide
Docker Nginx PHP MySQL: Your Ultimate Guide
Hey everyone! Today, we’re diving deep into the awesome world of Docker , specifically how to get Nginx , PHP , and MySQL working together like a dream. If you’re a developer, you know how crucial it is to have a reliable and consistent environment for your web applications. Setting up Nginx, PHP, and MySQL manually can be a real pain, with dependency issues and configuration headaches. But guess what? Docker comes to the rescue! It lets you package your application and its dependencies into a neat little container. This means your app runs the same way no matter where you deploy it. Pretty sweet, right? We’re going to walk through setting up a dynamic web stack using Docker, focusing on these three essential tools. By the end of this, you’ll be a pro at spinning up development environments faster than you can say “containerize it!”. Let’s get started and make your development life so much easier !
Table of Contents
- Setting Up Your Docker Environment
- Creating the Docker Compose File
- Configuring Nginx
- Setting Up the PHP-FPM Service
- Integrating MySQL Database
- Running Your Dockerized Application
- Managing Your Containers
- Advanced Tips and Troubleshooting
- Common Issues and Solutions
- Optimizing Your Setup
- Adding More Services
- Conclusion
Setting Up Your Docker Environment
Alright guys, the first thing we need to do is get our
Docker environment
all set up. If you haven’t already, you’ll need to install Docker on your machine. You can grab it from the official Docker website – just search for “Docker Desktop” for your OS (Windows, macOS, or Linux). Once it’s installed and running, you’re pretty much golden. The real magic for our
Nginx PHP MySQL
setup comes with
docker-compose
. This little utility lets you define and run multi-container Docker applications. It’s a game-changer because it uses a YAML file to configure your application’s services, networks, and volumes. Instead of running a bunch of individual
docker run
commands, you just run
docker-compose up
, and bam! Everything spins up. So, head over to the Docker documentation and install
docker-compose
if you don’t have it. It usually comes bundled with Docker Desktop these days, but it’s always good to check. Having a solid Docker foundation is key, and
docker-compose
is your best friend for managing complex setups like our web stack. Think of it as the conductor of your orchestra, making sure all the different instruments (containers) play in harmony. We’ll be creating a
docker-compose.yml
file shortly, which is where all the magic happens. This file will define our Nginx, PHP, and MySQL services, specifying their images, ports, volumes, and how they talk to each other. It’s all about automation and consistency, which are
super important
for developers trying to avoid the “it worked on my machine” problem. So, make sure Docker and Docker Compose are up and running smoothly before we move on to crafting our configuration.
Creating the Docker Compose File
Now for the fun part, guys! We’re going to create our
docker-compose.yml
file. This is the brain of our operation, where we define all the services for our
Nginx PHP MySQL
stack. Open up your favorite text editor and create a new file named
docker-compose.yml
in your project directory. Let’s break down what goes inside.
First, we need to specify the Docker Compose file format version. We’ll use version ‘3.8’ for this example, as it’s a recent and widely supported version. Then, we define our
services
. We’ll need three main services:
web
(for Nginx),
app
(for PHP-FPM), and
db
(for MySQL).
Here’s a basic structure to get us started:
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./src:/var/www/html
depends_on:
- app
app:
build: ./php
volumes:
- ./src:/var/www/html
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: your_database
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
Let’s dissect this piece by piece. The
web
service uses the official
nginx:latest
image. We map port 80 on our host machine to port 80 inside the container, so we can access our site. The
volumes
section is crucial. We mount our local
nginx/conf.d
directory to the container’s Nginx configuration directory, and we mount our application’s source code (
./src
) to the web server’s document root (
/var/www/html
). The
depends_on: - app
line tells Docker Compose that the
web
service should only start after the
app
service is running. Next, the
app
service is where our
PHP
magic happens. Instead of using a pre-built image, we’re using
build: ./php
. This means Docker will build an image from a
Dockerfile
located in a
./php
directory. We’ll create this
Dockerfile
next. We also mount our source code here, ensuring PHP can access it.
Finally, the
db
service uses the official
mysql:8.0
image. We set environment variables for the root password and the database name –
remember to change these to something secure
! We also define a named volume,
db_data
, to persist our MySQL data even if the container is removed. This is
super important
for development so you don’t lose your database content every time you restart. The
volumes: db_data:/var/lib/mysql
line mounts this persistent storage. The top-level
volumes: db_data:
block declares the named volume itself. This
docker-compose.yml
file is the blueprint for our entire
Nginx PHP MySQL
environment, making it incredibly easy to manage and replicate.
Configuring Nginx
Now that we have our
docker-compose.yml
file, let’s focus on the
Nginx
configuration. Remember in the
docker-compose.yml
, we mounted a local directory
./nginx/conf.d
to
/etc/nginx/conf.d
inside the Nginx container? This is where Nginx looks for its configuration files. We need to create this directory and put a configuration file inside it. So, in the same directory where your
docker-compose.yml
resides, create a new folder called
nginx
, and inside that, create another folder called
conf.d
. Inside
conf.d
, create a file named
default.conf
.
This
default.conf
file will tell Nginx how to handle incoming requests and how to pass PHP requests to our PHP-FPM service. Here’s a sample
default.conf
for a typical
PHP
application:
server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+);
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Let’s break this down, guys. The
server
block defines our virtual host.
listen 80
means Nginx will listen on port 80, which is what we mapped in
docker-compose.yml
.
server_name localhost
is simple enough.
root /var/www/html
points to the directory where our application code will be mounted.
index index.php index.html index.htm
tells Nginx which files to look for when a directory is requested. The first
location /
block handles requests for static files and pretty URLs.
try_files
checks if a file or directory exists, and if not, it passes the request to
index.php
. This is
super common
for frameworks like Laravel or Symfony.
The
real magic
for
PHP
integration happens in the
location ~ \.php$
block. This block matches any request ending in
.php
.
fastcgi_pass app:9000
is the most critical line here. It tells Nginx to pass PHP requests to the
app
service (which is our PHP-FPM container) on port 9000. Remember, in our
docker-compose.yml
, we named our PHP service
app
. This is how Docker Compose’s internal DNS resolves
app
to the correct container’s IP address.
fastcgi_index index.php
specifies the default PHP file to execute.
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
passes the correct script path to PHP. The
include fastcgi_params;
line includes standard FastCGI parameters. Finally,
location ~ /\.ht
blocks access to any files starting with
.ht
, which is a security best practice for files like
.htaccess
.
This Nginx configuration is pretty standard for serving dynamic
PHP
applications with Docker, and it integrates seamlessly with our
MySQL
database through the PHP application logic. You can customize
server_name
if you plan to use a specific domain, but for local development,
localhost
is usually fine. Make sure you have your actual PHP files inside the
./src
directory, and Nginx will serve them up beautifully. This setup ensures that Nginx handles static content and routing, while PHP-FPM processes all the dynamic scripting requests, creating a robust and efficient web server!
Setting Up the PHP-FPM Service
Okay, guys, let’s tackle the
PHP
part of our
Nginx PHP MySQL
stack! In our
docker-compose.yml
, we specified
build: ./php
for the
app
service. This means we need to create a
Dockerfile
in a directory named
php
(which should be in the same folder as your
docker-compose.yml
). This
Dockerfile
will define how to build our PHP-FPM image.
Create a directory named
php
, and inside it, create a file named
Dockerfile
. Here’s what we’ll put in it:
FROM php:8.2-fpm
# Install PHP extensions
RUN docker-php-ext-install pdo pdo_mysql mbstring mysqli && rm -rf /var/cache/apk/*
# Set working directory
WORKDIR /var/www/html
# Copy custom php.ini settings (optional)
# COPY php.ini /usr/local/etc/php/
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set permissions for the web directory
RUN chown -R www-data:www-data /var/www/html
# Expose port for PHP-FPM (default is 9000)
EXPOSE 9000
Let’s break this down, you amazing developers! We start with
FROM php:8.2-fpm
. This pulls the official PHP image with the FPM (FastCGI Process Manager) SAPI, which is
essential
for Nginx to communicate with PHP. We’re using version 8.2 here, but you can choose any version you prefer. The
RUN docker-php-ext-install pdo pdo_mysql mbstring mysqli && rm -rf /var/cache/apk/*
line installs some common and
very useful
PHP extensions:
PDO
and
PDO_MySQL
for database interactions,
mbstring
for multi-byte string support, and
mysqli
for MySQLi database access. The
rm -rf /var/cache/apk/*
is a clean-up step to reduce the image size. We then set the
WORKDIR
to
/var/www/html
, which is where our application code will live and where Nginx expects it.
Commented out is a line to copy a custom
php.ini
file if you need to tweak PHP settings like
memory_limit
or
upload_max_filesize
. If you need this, create a
php.ini
file in the same directory as your
Dockerfile
and uncomment that line. The next
RUN
command is
super important
for modern PHP development: installing
Composer
, the dependency manager. We use a multi-stage build here, copying the
composer
binary from the latest official Composer image directly into our PHP image. This keeps our final PHP image lean.
Finally,
RUN chown -R www-data:www-data /var/www/html
changes the ownership of the web root directory to the
www-data
user, which is the user PHP-FPM typically runs as. This prevents permission issues when your PHP application tries to write files or create directories. The
EXPOSE 9000
line documents that the container listens on port 9000, which is the default for PHP-FPM, and matches what we told Nginx in our
default.conf
.
With this
Dockerfile
, our PHP service is configured to run PHP-FPM and is ready to process requests passed from Nginx. Remember to place your actual PHP application files (like
index.php
) inside the
./src
directory. This PHP-FPM setup, working in tandem with Nginx and communicating with our
MySQL
database, forms the core of our dynamic web application stack. Building this custom PHP image ensures you have all the necessary extensions and tools for your project without relying on a generic, bloated setup. It’s all about control and efficiency, guys!
Integrating MySQL Database
Last but definitely not least, let’s get our
MySQL database
up and running! In our
docker-compose.yml
, we defined a
db
service using the official
mysql:8.0
image. This is the simplest way to get a MySQL server running in Docker.
Here’s the relevant part of our
docker-compose.yml
again:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: your_database
volumes:
- db_data:/var/lib/mysql
Let’s recap what this does, guys. We’re using the
mysql:8.0
image. The
environment
section is
critical
for initial setup.
MYSQL_ROOT_PASSWORD
sets the password for the MySQL
root
user.
Make sure you change
your_root_password
to a strong, unique password!
Seriously, don’t use this placeholder in production.
MYSQL_DATABASE
automatically creates a database named
your_database
when the container starts. You can change this to whatever you want your main database to be called.
The
volumes: - db_data:/var/lib/mysql
line is
extremely important
for data persistence.
db_data
is a Docker named volume. This means that the data stored in
/var/lib/mysql
inside the container (which is where MySQL stores its data files) will be persisted in a Docker-managed volume on your host machine. If you stop and remove the
db
container, and then start it again, your database schema and data will still be there! This is a
huge
advantage over ephemeral containers. Without this, you’d lose all your data every time the container restarts.
How do you connect your PHP application to this MySQL database?
Your PHP application code, running in the
app
container, can connect to the MySQL database using the service name
db
as the hostname. So, in your PHP code (e.g., using PDO or MySQLi), you would typically use:
-
Host:
db(this is the service name fromdocker-compose.yml) -
Port:
3306(the default MySQL port) -
Database:
your_database(the name you set inMYSQL_DATABASE) -
Username:
root -
Password:
your_root_password(the one you set inMYSQL_ROOT_PASSWORD)
For example, using PDO in PHP:
<?php
try {
$dsn = 'mysql:host=db;port=3306;dbname=your_database';
$username = 'root';
$password = 'your_root_password'; // Replace with your actual password
$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connected to MySQL database successfully!";
} catch (PDOException $e) {
die("Connection failed: " . $e->getMessage());
}
?>
Make sure your PHP code is placed in the
./src
directory, and your
index.php
or other entry point file is configured correctly in your Nginx
default.conf
. This
MySQL
integration is seamless; your PHP application can talk to the database just as if it were running on the same machine, thanks to Docker’s networking capabilities. You can even use GUI tools like DBeaver or MySQL Workbench to connect to your database from your host machine by forwarding the container’s port 3306 to your host (e.g., add
ports: - "3306:3306"
to your
db
service in
docker-compose.yml
temporarily), using
localhost
as the host and
3306
as the port, along with the
root
user and password you defined. This
Nginx PHP MySQL
stack is now fully functional and ready for development!
Running Your Dockerized Application
Alright guys, you’ve set up your
docker-compose.yml
, configured Nginx, created your PHP-FPM
Dockerfile
, and you’re ready to roll with MySQL. It’s time to bring your
Nginx PHP MySQL
stack to life! Navigate to your project directory in your terminal – the one that contains your
docker-compose.yml
file. Make sure your
./src
directory has at least an
index.php
file, perhaps with the PDO connection example we just discussed, so you can test the database connection.
To start all the services defined in your
docker-compose.yml
file, simply run the following command:
docker-compose up -d
Let’s break down this command, shall we?
docker-compose up
tells Docker Compose to build, create, and start the containers for all the services defined in your
docker-compose.yml
. The
-d
flag stands for “detached mode”. This means the containers will run in the background, and your terminal will be freed up so you can continue working. If you omit the
-d
, you’ll see all the container logs directly in your terminal, which can be useful for debugging but less convenient for normal use.
When you run this command, Docker Compose will:
-
Pull
the necessary images (like
nginx:latestandmysql:8.0) if they aren’t already present on your system. -
Build
your custom PHP image from the
Dockerfilein your./phpdirectory. - Create networks and volumes as defined.
-
Start
the containers in the correct order (respecting
depends_on).
Once the command completes successfully, you can open your web browser and navigate to
http://localhost
. If everything is configured correctly, you should see your PHP application running, or at least a success message if you used the database connection test code. You’ve successfully launched your
Docker Nginx PHP MySQL
environment!
Managing Your Containers
Running the stack is just the beginning, guys. You’ll need to know how to manage your containers. Here are some essential
docker-compose
commands:
-
docker-compose down: This command stops and removes all containers, networks, and volumes created bydocker-compose up. Use this when you want to completely tear down your environment. If you want to keep the volumes (for data persistence), you can usedocker-compose down --remove-orphansordocker-compose stopfollowed bydocker-compose rm -v(thoughdownis usually sufficient). -
docker-compose stop: This command stops the running containers but does not remove them. You can restart them later usingdocker-compose start. -
docker-compose start: Starts existing containers for the services. -
docker-compose restart: Restarts the services. -
docker-compose logs: View the output from containers. You can specify a service name, likedocker-compose logs web, to see logs only for Nginx. -
docker-compose logs -f: Follow the log output in real-time. Again, you can specify a service name. -
docker-compose ps: List the containers for the current project and their status. -
docker-compose build: Build or rebuild services. Use this if you make changes to yourDockerfileor any files included in the build context.
These commands are your bread and butter for working with your
Docker Nginx PHP MySQL
setup. For instance, if you modify your Nginx configuration file (
./nginx/conf.d/default.conf
), you’ll need to restart the Nginx container for the changes to take effect. You can do this with
docker-compose restart web
.
If you change your PHP code in the
./src
directory, the changes are usually reflected immediately because the volume is mounted. However, if you make changes to your
Dockerfile
or add/remove PHP extensions, you’ll need to rebuild the PHP image using
docker-compose build app
and then restart the services with
docker-compose up -d
or
docker-compose restart app
.
For the
MySQL
database, remember that data is persisted in the named volume
db_data
. This means you can stop, remove, and recreate the
db
container (
docker-compose down
followed by
docker-compose up -d
), and your data will remain intact. This is
crucial
for development workflows where you might frequently reset your application environment but want to preserve database changes. Mastering these commands will make managing your
Dockerized
web application a breeze, saving you tons of time and frustration compared to traditional local development setups. Keep these commands handy, and you’ll be a Docker pro in no time!
Advanced Tips and Troubleshooting
Alright, you magnificent developers, you’ve got your Docker Nginx PHP MySQL stack up and running! But what happens when things don’t go exactly as planned, or you want to level up your game? Let’s dive into some advanced tips and common troubleshooting scenarios.
Common Issues and Solutions
-
Connection Refused (Nginx to PHP-FPM):
This is often because Nginx can’t reach the PHP-FPM service. Double-check that
fastcgi_pass app:9000;in yournginx/conf.d/default.confcorrectly points to your PHP-FPM service name (app) and its port (9000). Ensure theappservice indocker-compose.ymlis correctly defined anddepends_onis set forwebto wait forapp. Also, verify that your PHPDockerfiledoesn’t have any errors preventingphp-fpmfrom starting. Checkdocker-compose logs appfor errors. -
Database Connection Errors:
If your PHP application can’t connect to
MySQL
, first check your credentials (
host,username,password,dbname) in your PHP code against theenvironmentvariables set in yourdocker-compose.ymlfor thedbservice. Remember, the hostname is the service name:db. Ensure thedbcontainer is running usingdocker-compose ps. Checkdocker-compose logs dbfor any MySQL startup errors. Also, confirm that thepdo_mysqlormysqliextension is installed in your PHPDockerfile. -
Permission Denied Errors:
If your PHP application can’t write files or create directories in your web root (
/var/www/html), it’s likely a file permission issue. Ensure your PHPDockerfilehas the lineRUN chown -R www-data:www-data /var/www/html. If you’re mounting volumes, sometimes host file permissions can interfere. You might need to adjust permissions on your host machine’s./srcdirectory or ensure theuserdirective inphp-fpm.confmatches your host user. -
Nginx 404 Errors:
A 404 error in Nginx usually means it can’t find the requested file. Check your
rootdirective innginx/conf.d/default.conf. Ensure it points to the correct location where your code is mounted (/var/www/html). Also, verify that yourindex.phpfile exists in the./srcdirectory and that yourlocationblocks are correctly configured, especially thetry_filesdirective for routing. -
Changes Not Reflecting:
If you update your PHP code and don’t see changes, it’s likely a caching issue. Since we’re using volume mounts (
./src:/var/www/html), changes should be reflected immediately. If not, try clearing your browser cache or restarting the PHP-FPM container (docker-compose restart app). If you changed yourDockerfileor Nginx config, ensure you rebuild/restart accordingly (docker-compose build app,docker-compose restart web).
Optimizing Your Setup
-
Custom PHP Versions and Extensions:
You can easily switch your PHP version by changing
FROM php:8.2-fpmin yourDockerfile. Need more extensions? Just add them to thedocker-php-ext-installcommand or usepecl installanddocker-php-ext-enablefor PECL extensions. Remember to rebuild your PHP image (docker-compose build app) after changes. -
Environment Variables:
Use environment variables extensively for database credentials, API keys, and other configurations. Instead of hardcoding them in
docker-compose.yml, you can use a.envfile and reference variables like${MYSQL_ROOT_PASSWORD}. Docker Compose will automatically load variables from a.envfile in the same directory. - Larger Projects: For larger projects, consider using separate Dockerfiles for different services or organizing your project structure more granularly. You might also want to explore tools like Docker volumes for shared code or caching.
-
Development vs. Production:
The setup we’ve discussed is
fantastic
for development. For production, you’d want to use more specific image tags (not
latest), implement proper security measures, use a production-ready web server like Apache or a more robust Nginx configuration, and potentially use a different database setup (e.g., managed services). You’d also avoid exposing database ports directly to the host.
Adding More Services
Docker Compose makes it
super easy
to add more services to your stack. Need Redis for caching? A message queue like RabbitMQ? Another database? Just add another service definition to your
docker-compose.yml
file, specify its image, environment variables, and ports, and
docker-compose up
will handle the rest! For instance, adding Redis:
services:
# ... (web, app, db services)
redis:
image: redis:alpine
ports:
- "6379:6379"
Then, your PHP application could connect to Redis using
redis://redis:6379
. This modularity is one of the biggest strengths of using Docker for your
Nginx PHP MySQL
stack. It allows you to easily scale and adapt your development environment to the specific needs of your project. By understanding these
advanced tips
and how to troubleshoot common issues, you can confidently build, deploy, and manage your web applications using Docker, making your development workflow
infinitely more efficient
and enjoyable. Keep experimenting, guys!
Conclusion
And there you have it, folks! We’ve journeyed through the essential steps of setting up a robust
Docker Nginx PHP MySQL
stack. From creating the foundational
docker-compose.yml
file to configuring Nginx, building a custom PHP-FPM service, and integrating a persistent MySQL database, you now have a powerful, reproducible development environment at your fingertips. We’ve seen how Docker, particularly with
docker-compose
, simplifies the often-complex task of managing multi-container applications, eliminating the dreaded “it works on my machine” syndrome and ensuring consistency across different development and deployment environments.
We covered crucial aspects like port mapping, volume mounting for code and data persistence, and inter-container communication using service names. You learned how to write an Nginx configuration to serve your files and pass PHP requests to PHP-FPM, and how to build a custom PHP image with necessary extensions and Composer. The MySQL setup ensures your data is safe and sound, ready for your applications to use.
Remember the commands to bring your stack up (
docker-compose up -d
), tear it down (
docker-compose down
), and manage your services (
logs
,
ps
,
restart
). We also touched upon common troubleshooting tips and advanced strategies like adding more services and preparing for production. This
Docker Nginx PHP MySQL
setup is not just about running a web server; it’s about embracing a modern, efficient, and scalable approach to web development.
By leveraging Docker, you’re investing in speed, reliability, and ease of maintenance for your projects. So go forth, containerize your applications, and enjoy the benefits of a streamlined development workflow. Happy coding, everyone! Your journey into the world of containerized development has just begun, and the possibilities are endless. Cheers!