Docker for the long-time Vagrant user (Laravel, Nginx, PHP with Xdebug, MySQL, Redis)

November 15, 2022 ≈ 5 minutes 14 seconds

Why did I decide to try Docker after being totally happy with Vagrant for such a long time? Well, I switched to the new dev machine, based on an M1 processor, which is unsupported by VirtualBox. Also for the speed, less disc consumption, ease of networking between containers and other benefits of containerized approach, such as when I'm working on a quick project with only a PHP built-in web server and need MySQL, Redis or Mailhog right away. With Docker, I just run a MySQL container attached to a volume, and that's it.

What to do with current Laravel Homestead virtual machines?

My initial thought was to use Laravel Sail which simplifies the process of creating a new Docker container. But I decided to build a container from scratch to be more fluent with Docker and be able to do more precise configuration like I did with Vagrant VMs. So, in this article, I'll go through the process of creating a Docker container for an existing Laravel project step-by-step. We'll use 'beaubus.test' as the project name and 'artist' as user.


1. Create folder structure

cd beaubus.test
mkdir -p .provision/mysql_datadir

We keep all Docker-related files inside .provision folder, to not bloat the project index with Docker configuration. Also, we use mysql_datadir/ folder as a data directory for MySQL to persist data when we remove the container.


2. Download .sql files from production

# On production
sudo mysqldump beaubus > beaubus.sql
# Download into .provision/ folder

3. Copy id_rsa.pub into .provision

cp ~/.ssh/id_rsa.pub .provision

We need public key to connect to the container via SSH. We also copy it to the .provision/ folder because we cannot use it directly (paths outside of build context are not allowed in Dockerfile)


4. Create .provision/nginx.conf

# beaubus.test

server {
    server_name .beaubus.test www.beaubus.test;

    listen 443 ssl;
    ssl_certificate /var/www/default/.provision/nginx-beaubus-selfsigned.crt;
    ssl_certificate_key /var/www/default/.provision/nginx-beaubus-selfsigned.key;

    if ($host = beaubus.test) {
        return 301 https://www.beaubus.test$request_uri;
    }

    root /var/www/default/public;

    index index.html index.htm index.php;

    charset utf-8;

    location = /favicon.ico { log_not_found off; access_log off; }
    location = /robots.txt  { log_not_found off; access_log off; }

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.0-fpm.sock;
    }

    error_page 404 /index.php;
}


server {
    server_name .beaubus.test www.beaubus.test;

    return 301 https://www.beaubus.test$request_uri;
}

5. Create .provision/my_overrides.cnf

[mysqld]
datadir = /var/www/default/.provision/mysql_datadir
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

We tell mysqld to use the data directory that we created earlier it the .provision/ folder.


6. Create .provision/xdebug.ini

;add extension for zend engine (php language core)
zend_extension = xdebug.so
;color output of var_dump in cli mode
xdebug.cli_color = 1
; Enables Step Debugging. This can be used to step through your code while it is running, and analyse values of variables.
xdebug.mode = debug
xdebug.idekey = phpstorm
;gateway from route -n command (IP or hostname of machine running IDE)
xdebug.client_host=host.docker.internal
xdebug.client_port=9000

'host.docker.internal' is a special DNS name which resolves to the internal IP address used by the host


7. Create .provision/beaubus-worker.conf

Which is configuration file for supervisor running Laravel workers.

[program:beaubus-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/default/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
umask = 002
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/default/storage/logs/worker.log
stopwaitsecs=3600

8. Create .provision/Dockerfile

We add after.sh to entrypoint and make it executable, so it runs automatically every time we run a container.


9. Create .provision/after.sh

#!/bin/bash

cd /var/www/default

sudo service php8.0-fpm start
sudo service redis-server start
sudo service ssh start


# check for beaubus file in MySQL datadir an if not, provision mysql
if [ ! -d /var/www/default/.provision/mysql_datadir/beaubus ]
then
    chown mysql:mysql /var/www/default/.provision/mysql_datadir
    chmod 750 /var/www/default/.provision/mysql_datadir
    sudo mysqld --initialize-insecure --user=mysql
    sudo service mysql start

    echo "Setting up MySQL..."
    sudo mysql -e "CREATE USER \`homestead\`@'%' IDENTIFIED BY 'secret'"
    sudo mysql -e "CREATE DATABASE \`beaubus\`"
    sudo mysql -e "grant all privileges on *.* to 'homestead'@'%'"
    sudo mysql -e "FLUSH PRIVILEGES"
    sudo mysql beaubus < .provision/beaubus.sql
else
    sudo service mysql start
fi

# run supervisor with website worker
sudo service supervisor start

sudo service nginx start

# generate wildcard certificate for beaubus.test if it's not exist
if [ ! -f .provision/nginx-beaubus-selfsigned.key ] || [ ! -f .provision/nginx-beaubus-selfsigned.crt ]
then
    openssl req -x509 -nodes -days 3650 -subj "/C=CA/ST=QC/O=BEAUBUS/CN=BEAUBUS" -newkey rsa:2048 -keyout .provision/nginx-beaubus-selfsigned.key -out .provision/nginx-beaubus-selfsigned.crt -addext "subjectAltName=DNS:beaubus.test,DNS:*.beaubus.test,IP:127.0.0.1"
fi

After creating, make it executable: chmod u+x after.sh


10. Build an image

docker build -t beaubus-image .provision

--tag , -t Name and optionally a tag in the ‘name:tag’ format The .provision at the end of the docker build command tells that Docker should look for the Dockerfile in .provision directory


11. Run the container

docker run -it -p 80:80 -p 443:443 -p 3306:3306 -p 2222:22 -e TZ=UTC+4 --rm --name beaubus-app -v "$(pwd)":/var/www/default beaubus-image

-d Run the container in detached mode (background) --rm Automatically remove the container when it exits --name Assign a name to the container -p 2222:22 Map 22 port of container to 2222 on host

To access console run docker exec -it beaubus-app /bin/bash To access console via SSH run: ssh artist@localhost -p 2222 To read log run docker logs -f beaubus-app To run Laravel tests: docker exec -it beaubus-app php artisan test --testsuite=Acceptance (testsuites specified in phpunit.xml) To run composer install: docker exec -it beaubus-app composer install To run tinker : docker exec -it beaubus-app php artisan tinker


12. Add environmental variables into phpunit.xml

<server name="PHP_IDE_CONFIG" value="serverName=beaubusconsole"/>
<env name="XDEBUG_CONFIG" value="xdebug.idekey=phpstorm"/>

'beaubusconsole' is the server from PHP > Servers in PhpStorm


13. Configure PhpStorm

— Add CLI Interpreter 'artist@localhost:2222', php located in /usr/bin/php — Add CLI Interpreter in PHP > Test Frameworks — Add XDEBUG_CONFIG="xdebug.idekey=phpstorm" into PHPUnit configuration template — Add 'beaubusconsole' server with host 172.17.0.2 (IP of Docker container) and port 22 — Add beaubus.test, port 443

To get Docker container ip, run docker inspect -f "{{ .NetworkSettings.IPAddress }}" beaubus-app -f Format the output using the given Go template


14. Add https certificate on macOS

Open 'keychain Access.app'. Open 'Login -> Certificates' tab. Drag .crt file from '.provision' folder. Double click on each dragged file -> Trust -> SSL -> Always trust


15. Add to .gitignore

/.provision/mysql_datadir
/.provision/*.pub
/.provision/*.crt
/.provision/*.key

That's it. Thank you for reading!

I intentionally didn't comment on configuration files, assuming that the reader will have prior experience with Vagrant boxes and manual configuration of common services. If you find this article useful, please share it and click on ❤️ in the top right corner to let me know.



Reference to command line commands: docker | Docker Documentation

Subscribe to our newsletter

If you provide url of your website, we send you free design concept of one element (by our choice)

Subscribing to our newsletter, you comply with subscription terms and Privacy Policy