Getting Started: Craft CMS Local Development with Docker

Damien SmrtDamien Smrt

Damien Smrt

Local environments can be tough. I want anyone on my team to be able to pull down a project repository and get started without worrying about environmental differences. Like many, I resisted Docker due to the frustration of always "having to learn one more &$#*ing thing?!". I started diving into Docker a year ago and haven't looked back. There is a learning curve, it's different, and you will find yourself wondering "how do I do this with Docker?!" (a lot). But the more I have learned, the more I am certain it is worth the hype. Containers give us the promise of a better and more controlled infrastructure. One that is more isolated, portable, and disposable. Let’s dive in and take a look at how we have bootstrapped our local development with Docker at Flipbox Digital.

Docker Compose && docker-compose.yml

When you install Docker for Mac or Windows (via Docker Desktop), Docker is installed with other tools like Docker Compose. Docker Compose makes it easy to build your stack. Take a look at the following docker-compose.yml file:

version: '3'
        image: 'flipbox/php:72-apache'
            - '80:80'
            - '443:443'

            - '.:/var/www/html/'
            ENVIRONMENT: dev
            DB_SERVER: db
            DB_USER: craft
            DB_PASSWORD: craft
            DB_DATABASE: craft
            XDEBUG_ON: "yes"
            XDEBUG_HOST: "host.docker.internal"
            APACHE_DOCUMENT_ROOT: "/var/www/html/web"
        image: 'mysql:5.7'
        restart: always
            - '3306:3306'
            MYSQL_ROOT_PASSWORD: password
            MYSQL_DATABASE: craft
            MYSQL_USER: craft
            MYSQL_PASSWORD: craft

We place this file within the root of our Craft CMS project repository. Let's break down this configuration.


Above we have a 'web' and 'db' service. This is our stack so when we start the containers, we can reference them by those names, 'web' and 'db'. Docker creates a network and assigns the service names as hostnames. This is handy when we are pointing Craft CMS (the "web" container) to MySQL or Postgres (the "db" container). We use 'db' as the server hostname instead something like localhost.

Services > Image

We define our image here which points to a remote repository. Our PHP image extends the latest PHP 7.2 Apache image from the official PHP Docker Hub. Our code for building the image is open source and on our GitHub.

Docker has a ton of support. Almost any image you can think of is available: Redis, Memcached, Postgres, DynamoDB, and many more. Images can have some really cool features baked in as well. A good tip is to look at their environmental variables to see what you can use to easily customize the running container.

Services > Ports

We have control over exposing ports and mapping them to our host (which with local development, is our localhost). I want to be able to connect to the MySQL database using Sequel Pro, so we expose port '3306' and map it to '3306', our localhost. Since we are mapping ports like 3306 and 80 to localhost, this can present a port conflict if something else is running on one of those ports. Make sure to shutdown any other projects running containers or local servers using those ports.

Services > Volumes

Examining the configuration for volumes is straight forward. Volumes are configured by setting the source path on your host (your computer) to the destination path on the container. We use volumes heavily in local development so we can share our code with the container while developing it. If you have a large codebase, you may see issues with performance on MacOS and presumably Windows as well. On Mac and Windows, Docker runs a virtual machine to run the containers in Linux and sharing those files can be expensive. This is just something to be aware of. It's not the end of the world. There are complicated solutions so go crazy if you feel it's worth the effort.

Quick Tip

#Check on your mac
sudo lsof -i tcp:3306

#Check if docker containers are using the port
docker ps

To reiterate, we place this file within our project repo and run docker-compose up. The "up" command pulls the images from the docker repository and starts the services. Since a container is only responsible for one process, you will see any logging the process returns. In this case you'll see MySQL and Apache logging.

docker-compose up || docker-compose up -d

Start Your Stack

#Starting for the first time or updating any image or configuration changes
docker-compose up
docker-compose up -d #daemon mode

#You can run `start` thereafter
docker-compose start

The stack is now running! To check your web server go to http://localhost and to connect to the database through your preferred client, point to localhost and port 3306 (when using the configuration above). If you ran docker-compose up and are done with the stack, Ctrl+C to kill the running containers. To verify the services aren't still running, run docker-compose ps and viewing the "State" column, which will look for the docker-compose.yml configuration within your current directory. Furthermore, running containers can be checked globally by executing a docker ps. If you have a stubborn container that is running when it shouldn't or you are too lazy to change directories and stop containers gracefully, grab the container id with docker ps and run docker kill <container id>.

Quick Tip

#vim /etc/hosts

Check out our Docker image and Dockerfile within our Github repository. Engage with us on social media at @flipboxdigital and @dsmrt. Let us know how you are working with Docker and what else you'd like us to discuss.