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

November 15, 2022 ≈ 6 minutes 16 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


FROM ubuntu:20.04
WORKDIR /var/www/default
RUN apt-get update

# add-apt repository command and repo with php versions, install php with extentions
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:ondrej/php
RUN apt-get update && apt-get install -y php8.0-fpm php8.0-curl php8.0-gd php8.0-imagick php8.0-mbstring php8.0-mysql php8.0-zip php8.0-xml php8.0-bcmath php8.0-redis php8.0-xdebug

# install nginx
RUN apt-get install -y nginx

# install mysql
RUN apt-get install -y mysql-server
# assign home directory to mysql user, to get rid of error
RUN usermod -d /var/lib/mysql mysql
# comment bind-address line
RUN sed -iE 's/^bind-address/#bind-address/g' /etc/mysql/mysql.conf.d/mysqld.cnf
# trailing slash is important
ADD my_overrides.cnf /etc/mysql/mysql.conf.d/

# add xdebug config
ADD xdebug.ini /etc/php/8.0/mods-available

RUN apt-get install -y sudo
RUN apt-get install -y redis-server
RUN apt-get install -y supervisor
RUN apt-get install -y openssl

# config for ssh, to use php interpreter inside PhpStorm
RUN apt-get install -y openssh-server -y
RUN groupadd artist && useradd -ms /bin/bash -g artist artist
RUN usermod -aG www-data artist
RUN usermod -aG sudo artist
RUN echo 'artist  ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
# > -m  create home dir, -s  specify shell, -g    user group
RUN mkdir -p /home/artist/.ssh
COPY id_rsa.pub /home/artist/.ssh/authorized_keys
# > only path relative to build context can be used
RUN chown artist:artist /home/artist/.ssh/authorized_keys

# copy composer binary from composer image from docker hub
COPY --from=composer/composer:latest-bin /composer /usr/bin/composer

# add nginx config for website
ADD nginx.conf /etc/nginx/sites-available/default

# add supervisor config with website worker
ADD beaubus-worker.conf /etc/supervisor/conf.d

# container would run under artist user
USER artist:artist

# provision and run shell
ENTRYPOINT cd /var/www/default/.provision && ./after.sh && cd .. && /bin/bash

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