DEV Community

truthseekers
truthseekers

Posted on

Setup a basic Local PHP Development Environment in Docker

In this post we'll learn how to setup a basic local PHP development environment in Docker. We'll be using docker-compose and Dockerfile to achieve this. We'll use PHP, Apache, and Mysql. Setting up a comprehensive environment with things like task runners, composer, etc... will come in later tutorials.

Learn to code with our beginner friendly tutorials

If you just want the quick and dirty source code, then skip to the bottom. We're going to take this nice and slowly going step-by-step.

Pre-Requisites: Docker & Docker-compose installed.

First step: Get a simple PHP script to run with Dockerfile.

If you take a look at the PHP Docker docs you won't see anything about a docker-compose file, but we can figure out how to do it with what they do give us. At the very beginning they give us a simple Dockerfile setup. Let's use that for our Dockerfile in the root of our project directory: I'm calling my project "php-docker". Then inside make a Dockerfile with this code:

FROM php:7.4-cli
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
CMD [ "php", "./index.php" ]

This code is the setup to create the image we'll use. This setup pulls in PHP 7.4 with some command line stuff.

The COPY . /usr/src/myapp copies the contents of the current directory into /usr/src/myapp inside the Docker container. The next line: WORKDIR /usr/src/myapp sets the /usr/src/myapp as the "working directory", kind of like you would cd /to/your/project.

Then from inside the /usr/src/myapp folder we run the command $ php ./index.php where index.php is our script. So that means we need a PHP script. Let's create our index.php file inside the project directory like this:

<?php

echo "Hello from the docker container";

Now we can continue with their instructions, which is to build and run the docker image:

$ docker build -t my-php-app .
$ docker run -it --rm --name my-running-app my-php-app

The first command is going to build the image with the name "my-php-app" on your computer using the contents of the current directory. The second command prepares a container called "my-running-app based off the image we just created "my-php-app". We setup to run the index.php script, so our script runs in the command line. We should get something like this:

First step accomplished! But simple PHP scripts aren't very useful. Let's set this up on an Apache web server. If you scroll further down the documentation you'll see an "Image variants" section with one of them being php-apache. This image has Apache bundled with PHP. This will allow us to easily get our script running on a webserver and display the script results in a browser. Again, let's follow the directions. To run the Apache/php image WITHOUT a dockerfile, run this command: (Note the first line is for Linux/Mac users and the 2nd line is for the lonely Windows users

docker run -d -p 80:80 --name my-apache-php-app -v "$PWD":/var/www/html php:7.2-apache # This line for *nix users
docker run -d -p 80:80 --name my-apache-php-app -v C:\Users\fastp\Desktop\Code\tutorials\php-docker:/var/www/html php:7.2-apache   # For Windows users

This line is basically saying RUN the container and set the name as "my-apache-php-app. -p is to set the PORT mapping from our local machine to the port on the container. the left side is our local port, the right side is the port on the container. Then use -v to set the VOLUME, or... basically bind our present working directory to the /var/www/html folder. This essentially puts the contents of our current directory into the html directory on the container so our code can run inside it. At the end we define the image we build from, which is php:7.2-apache

Note that Windows doesn't have a $PWD command, so I had to manually put my path for this to work.

Okay, so we setup to have our script run on port 80, so go ahead and visit http://localhost:80 and you should see your script running in the browser.

PHP and docker-compose

Now let's start moving stuff into a docker-compose file, but first let's stop that container we just created. You can run a docker ps to get the container id and then run $ docker stop container_id but I'm just going to stop all containers like this:

docker stop $(docker ps -a -q)

Now create a docker-compose.yml file in the root of your project and put the following inside:

version: '3.1'

services:
  php:
    image: php:7.4-apache
    ports:
      - 80:80
    volumes:
      - ./src:/var/www/html/

The top line just sets the version number of docker-compose we're using. Then we have the "services" which is the list of containers to setup. We'll be giving this container the name of "php" so other containers can connect via that name. The "image" will be php:7.4-apache (even though our previous command line experiment used 7.2)

Then just like in our command line trial we're setting up the PORT mapping from 80 on our local machine to 80 on the container. Finally we'll stuff the contents of our ./src folder into the containers /var/www/html folder. Now let's move our script into the ./src folder on our local machine. My project structure now looks like this:

Now run docker-compose up -d from inside the project root and visit localhost:80 and see the script running n the browser. Because we're using volumes to stick our code in the container we should be able to change the script and have it update automatically. Give that a try.

Now let's shut down our container with docker-compose down.

The next step is to setup MySQL and Adminer (a DB access tool) Luckily their docs give us a docker-compose example so we don't have to think as much. We do need to modify the settings a tiny bit though because to connect PHP to MySQL we need to install a few missing pieces to our PHP environment. Here's what our docker-compose.yml file should look like now:

# Use root/example user/password credentials
version: '3.1'

services:
  php:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 80:80
    volumes:
      - ./src:/var/www/html/

  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: example

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

Our PHP service looks different because to connect PHP to the database using mysqli we need to install some mysqli stuff. That means we need to use a Dockerfile to customize our php7.4-apache image. So now we're using "build" to use the contents of the current directory (the dot) and using our Dockerfile to create the image. (Dockerfile image code below)

So now we've added a "db" service which is the mysql image. They use some weird command that has something to do with the password :-) and a restart policy. They also setup an environment variable which sets the root password to "example", so that's how we'll log in.

Then adminer gets the adminer image and sets that port mapping to 8080:8080. Just like in XAMPP how you go to localhost/phpmyadmin to get the database management tool, well... with this you go to localhost:8080 to get to the adminer db tool.

Let's also setup Mysqli to work inside our PHP container. Here's my Dockerfile code:

FROM php:7.4-apache
RUN docker-php-ext-install mysqli

I know that's a lot of code to process.... So let's take it line-by-line. The first line uses the php:7.4-apache image to build the container. The second line runs a command to install the mysqli extension inside the container. From here we have everything we need to have a super basic PHP development environment. So let's run docker-compose up -d again.

Now we should be able access Adminer and log in. I'll just use a few images to speed things up. Just note that the name of the "server" is "db" in our case based on the dockerfile. We could have named it "pie" or anything we wanted.

Select "Create Database"

Name it and hit save. I named mine "company1"

Then select "new item" to add a few items to your table.

Now we just need the PHP to connect to our database. This isn't a PHP tutorial so I won't explain it all, but it just connects to our database and runs some queries and prints them on the screen. The important part is the third line that connects MySQL to PHP.

<?php

echo "Hello from the docker yooooo container";

$mysqli = new mysqli("db", "root", "example", "company1");

$sql = "INSERT INTO users (name, fav_color) VALUES('Lil Sneazy', 'Yellow')";
$result = $mysqli->query($sql);
$sql = "INSERT INTO users (name, fav_color) VALUES('Nick Jonas', 'Brown')";
$result = $mysqli->query($sql);
$sql = "INSERT INTO users (name, fav_color) VALUES('Maroon 5', 'Maroon')";
$result = $mysqli->query($sql);
$sql = "INSERT INTO users (name, fav_color) VALUES('Tommy Baker', '043A2B')";
$result = $mysqli->query($sql);


$sql = 'SELECT * FROM users';

if ($result = $mysqli->query($sql)) {
    while ($data = $result->fetch_object()) {
        $users[] = $data;
    }
}

foreach ($users as $user) {
    echo "<br>";
    echo $user->name . " " . $user->fav_color;
    echo "<br>";
}

Now here is the result of our code when we run it in the browser. :

Persistent Mysql using Volumes:

You're probably not going to build your entire app in one sitting so we'll want to use volumes for persistent data like the stuff inside your database. Without a volume your data will be erased every time you stop the containers. In order to do that you just need to add a few more lines to your code. The code from db: and down will look like this: (The important lines are at the end of the "db" section where it says volumes, and the VERY end where it says "volumes"

  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: example
    volumes:
      - mysql-data:/var/lib/mysql

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

volumes:
  mysql-data:

Note the indentation. volumes will be at the same indentation level as "services".

I won't go deep into volumes right now. (To be honest I'm no expert yet lol) but in this code we're setting up a volume on our system (managed by docker) called mysql-data. All our database tables and junk gets stored inside the "mysql-data". Then we map that data to /var/lib/mysql on the container.

So that's pretty much it y'all. If you want to get in some practice you should try to swap out MySQL for Postgres. Or Apache for Nginx, or try to install some other tools like Composer using the Dockerfile. Please let me know if the article helped or not. Would love some feedback so I can make these better.

Here's the source code

Learn to code with our beginner friendly tutorials

Top comments (8)

Collapse
 
stsmuniz profile image
Salustiano Muniz • Edited

I was really stuck trying to make a docker-compose with httpd and php separated, but you helped me a lot

Also, in case anyone have an "internal server error", add

RUN a2enmod rewrite

to your Dockerfile

Collapse
 
czuidema profile image
chriszui • Edited

For macOS users that cannot connect to the localhost:
The docker containers do not run on the host machine itself but in a VM.
I used the following workaround:
1.) Find the IP with the command 'docker-machine ip default' in the terminal.
2.) The command returns something like '192.168.99.100'.
3.) Use this IP instead of 'localhost'. Access the port 80 by typing '192.168.99.100:80' into the browser. Or '192.168.99.100:8080' for the port 8080.

Collapse
 
alokverma75 profile image
alokverma75

This is the best tutorial for PHP and mysql combination. I tried many but none worked and after this I was able to run, and could understand all details from video. Many thanks for posting this tutorial.

Collapse
 
peppelauro profile image
peppelauro

Good tutorial!
If you want a PWD-like in Windows you can use %cd% system variable ;)

Collapse
 
chuckwood profile image
Charles Wood

%cd% works for cmd, but if you're in PowerShell you can use $PWD :)

Collapse
 
vraskin profile image
vraskin • Edited

Thank you for the tutorial.
I was getting "Could not open input file: ./index.php" when I add Mysql part to docker-compose.yml. I added "working_dir: /var/www/html/" to my Dockerfile. Now, it is not finding mysqli class.

Solved:
Do not forget to rebuild the container after you add mysqli

docker-compose up --build

Collapse
 
adellinocasas profile image
adellinocasas

Brilliant this tutorial. To whom is starting with Docker this is a
outstanding reference.

Collapse
 
abrandao profile image
Anderson Brandão • Edited

Thank you so much!

I am back to PHP because a very old project and this article really helped me.

I adition: I really like Adminer.