This article (and the next) explains, how you can set up a local development environment for your web development needs. It aims especially at PHP development, since I had some projects to work on that were developed some time ago, but still need maintenance now and then.

What I need for most of my older projects are the following features (but not always all of them):

  • An Apache Server to show static HTML pages
  • PHP to interpret dynamic pages
  • MySQL as data storage
  • xDebug to be able to put breakpoints into my code and step through it

I need to mention first, that it really helps, if you know how to change settings for Apache and/or PHP in a linux environment. Also how to use mysqldump from the command line. With this knowledge, you can quickly reconfigure the basic docker containers to your custom needs. But I think even if you miss this knowledge, you can pick up enough to survive from this article.

For this article, I assume that you already have docker installed on your machine.

My project folder structure

Lets take this folder structure as an example. The "real" project code resides in the folder src. Lets assume it should read a list of items from a MySQL database and display it. I made a data export of my MySQL database beforehands (maybe through PHPMyAdmin) and saved it in a folder beside the project code. Make sure to enable the option "create database if it does not exist" before you make your dump.

.
+-- data
|   +-- dump.sql
+-- src
|   +-- index.php
+-- docker-compose.yml

So to start this project locally, we need Apache+PHP and a MySQL server. And they need to talk to eachother. Lets go :)

The docker-compose file is your friend

This single file makes your individual setup by project a breeze. Keep in mind: only ident the file with spaces. Never use tabs. It breaks everything. Lets take a look at the file we want to use, here:

version: '3'

services:
  db:
    image: mariadb/server:10.3
    environment:
      MYSQL_ROOT_PASSWORD: root
    ports:
      - "3306:3306"
    volumes:
      - ./data/dump.sql:/docker-entrypoint-initdb.d/dump.sql
  web:
    image: php:7.2.2-apache
    container_name: php_web
    depends_on:
      - db
    ports:
      - "80:80"
    volumes:
      - ./src/:/var/www/html/

The file does this: It starts two docker containers (services:). One, called db, which is based on a MariaDB image (in case you don't know MariaDB: its a MySQL fork that is just SO MUCH BETTER. And doesnt't come with Oracle license chains). Docker will download and start that image automatically. We set the password for the MySQL root user to "root". Thats fine for local dev. Take some other pwd if you are paranoid.

The docker container will run in an isolated environment. But for administrative reasons, we might want to connect to the MySQL server to check the data or manually run queries against it. On Windows, I can recommend HeidiSQL for that job. To be able to access the server inside the container, we need to expose the port to our computer. That does the ports statement. 3306:3306 means: Take port 3306 from inside the container (left side of the colon) and make it available on my machine on port 3306 (right side of the colon).

The MariaDB container has a special feature: When it gets started, it looks into its internal file system into the folder /docker-entrypoint-initdb.d/ (yes, thats a folder name) and looks for *.sql files inside. If it finds some, it will interpret them. So what we need to do is pushing our SQL file we saved in ./data/dump.sql into that folder inside the docker container. This is done through the volumes setting. The satement ./data/dump.sql:/docker-entrypoint-initdb.d/dump.sql tells docker: take my lokal file ./data/dump.sql (left side of the colon) and make it available inside the container under the path /docker-entrypoint-initdb.d/dump.sql (right side of the colon). When the container boots, our SQL dump will be interpreted.

So we took the vanilla (empty) MariaDB container, defined a root password, forwarded the connection port and mounted the sql dump into it. The server will start and our database with all data will be created.

Now the web server itself - the container is identified as web. It takes the image php:7.2.2-apache. I gave the container a specific name ( php_web), since I often access it via commandline to install new PHP modules or reconfigure things. With a specific name set up, entering the container is much easier. But more on that topic later in the article.

We tell docker, that this container needs to make use of the previously created db container. Docker will make sure that the database is there, before the web server is started. Then we expose port 80 on our machine so that we an actually call http://localhost in our browser.

In the end, we mount our local ./src folder into the container into the place where apache looks for data: /var/www/html. Yes, the whole folder, not just a file, like above with the dump.sql. The nice thing: If I put new files into my local ./src folder, modify or delete them, the same thing happens inside the docker container. So when I modify some sources and reload my page in the browser, I see my changes.

Now since we have defined our two containers, lets start them. Open your console, navigate to your project folder and execute: docker-compose up.

You will see some log outputs and at the end hopefully something like 'apache2 -D FOREGROUND'- thats the apache run command inside the web server container.

If you call http://localhost now, you see something!

That escalated quickly

What the hell? Well, I mentioned above that our PHP script wants to read a list of items from the MySQL (or MariaDB) server. To do that, it uses a PHP module named MySQLi. Well... The module is not installed in our vanilla docker container :)

Install additional php modules

There is a way to install PHP modules inside the container. At first, we need to gain command line access into the container. Open your console and call: docker exec -ti php_web bash. Congratulations, you entered the command line inside the container! You should see something like root@127174b004ec:/var/www/html# This means we are currently inside the /var/www/html folder inside the container. If you type ls, you will see all your files from under ./src in your project folder.

Now lets install the mysqli php extension. You call docker-php-ext-install mysqli. The people who prepared that PHP container image already included this handy command, so you just need to call it. If you want to see which modules can be installed with that command, just call it without a module name at the end. Its quite a lot.

To enable the PHP module, you need to restart apache inside the container. Call apachectl restart. And don't get a shock - you will be kicked out of the console afterwards. But if you now reload the page in your browser, PHP can actually use mysqli and display the list of items from the database.

Please be aware that if you destroy your containers by using docker-compose rm, all your custom modules are gone again. To make them persistent, we need to automate the installtion, or create our own docker image.

In another blog post, I will cover how you can automate that installation of custom PHP modules or automatically reconfigure PHP or Apache. I will also explain how you can set up xDebug to debug your PHP application inside the container.