Ahmed Ashraf

Hello Friends, I want to share something that I used to do a few months ago for my laravel projects.

The reason I decided to drop Valet is that I'm using a shared Macbook pro that has the company dev environment and the projects I'm working on every day after work.

Valet was not quite a good option as it somehow conflicts with the tools I use for my company dev setup. so I decided to dockerize everything to make it easy for me to work and even share projects with other friends if we work on it together.

here I share how I set up every project I start.feel free to share your thoughts, ideas, comments and feedback on it. also, I know it's not that fast to get it for every project. but as a template, it does a good job for the work.

Prerequisite

The setup I use can be for new projects or existing projects. tools used is docker and other project dependencies like MySQL, Redis, NPM for frontend development.

I'll start everything with an empty directory and you can jump between steps. also, I assume you already have docker and docker-compose on your machine and you can read docker-compose.yaml and Dockerfiles

Preparing the files

I'll start with 4 images

  • Nginx: as a web server
  • PHP: contains php-fpm, php-cli, composer. I even install npm there to easy run compile laravel assets instead of installing another image
  • MySQL: the database we need for our app
  • Redis: for caching, queues
    .
    ├── docker
    │   ├── nginx
    │   │   └── conf.d
    │   │       └── default.conf
    │   └── php
    │       └── Dockerfile
    └── docker-compose.yml
    
    4 directories, 3 files

We have docker-compose.yml and docker directory that has the configuration files. and for php we have a custom image

The docker-compose

We will define some volumes to copy important configurations and to keep data in sync between our machine and the containers so even with a rebuild or restart containers the data still safe and not destroyed.

Check the comments in the file to know more

    version: '2'
    services:
      webserver:
        image: nginx:alpine
        volumes:
          - ./code:/var/www
    			## copy nginx configuration for our application ##
          - ./docker/nginx/conf.d/:/etc/nginx/conf.d/
        ports:
    			## run the webserver on port 8080 ##
          - "8080:80"
      app:
    		## read php image from our custom docker image ##
        build:
          context: .
          dockerfile: ./docker/php/Dockerfile
        volumes:
          ## copy project files to /var/www ##
          - ./code:/var/www
        working_dir: /var/www
    
      db:
        image: mysql:5.7
        ## expose the mysql port to our machine so we can access it from any mysql-client like TablePlus ##
        ports:
          - "3388:3306"
       ## keep mysql data on localhost so we don't lose them ##
        volumes:
          - ./docker-volumes-data/db:/var/lib/database
        environment:
          MYSQL_DATABASE: laravel_db
          MYSQL_ROOT_PASSWORD: root
      redis:
        image: redis
        volumes:
          ## keep redis data on localhost so we don't lose them ##
          - ./docker-volumes-data/redis:/data

Now let's jump into each one and check it's content

Nginx: webserver

    webserver:
        image: nginx:alpine
        volumes:
          - ./code:/var/www
    			## copy nginx configuration for our application ##
          - ./docker/nginx/conf.d/:/etc/nginx/conf.d/

here we copy the project files and also copy all conf.d files. which has one file called default.conf and the file has the virtual host for our project

    server {
    	client_max_body_size 30M;
        listen 80;
        index index.php index.html;
        error_log  /var/log/nginx/error.log;
        access_log /var/log/nginx/access.log;
        root /var/www/public;
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass app:9000;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
        }
        location / {
            try_files $uri $uri/ /index.php?$query_string;
            gzip_static on;
        }
    }

PHP

    app:
        ## read php image from our custom docker image ##
        build:
          context: .
          dockerfile: ./docker/php/Dockerfile
        volumes:
          ## copy project files to /var/www ##
          - ./code:/var/www
        working_dir: /var/www

for php we have a custom dockerfile for it so we can install the extensions we need and customize the setup for our need. the file fits any project I work on so it might fit your need to

    # dockerfile
    FROM php:7.2-fpm
    
    # Copy composer.lock and composer.json
    COPY code/composer.lock* code/composer.json* /var/www/
    
    # Set working directory
    WORKDIR /var/www
    
    # Install dependencies
    RUN apt-get update && apt-get install -y \
        build-essential \
        libpng-dev \
        libjpeg62-turbo-dev \
        libfreetype6-dev \
        locales \
        zip \
        jpegoptim optipng pngquant gifsicle \
        unzip \
        curl
    
    # Clear cache
    RUN apt-get clean && rm -rf /var/lib/apt/lists/*
    
    # Install extensions
    RUN docker-php-ext-install pdo pdo_mysql mbstring zip exif pcntl
    RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
    RUN docker-php-ext-install gd
    RUN pecl install -o -f redis \
        &&  rm -rf /tmp/pear \
        &&  docker-php-ext-enable redis
    
    # Install composer
    RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    
    # NPM for frontend builds
    RUN apt install nodejs npm -y
    
    # Add user for laravel application
    RUN groupadd -g 1000 www
    RUN useradd -u 1000 -ms /bin/bash -g www www
    
    # Copy existing application directory contents
    COPY ./code /var/www
    
    # Copy existing application directory permissions
    COPY --chown=www:www . /var/www
    
    # Change current user to www
    USER www
    
    # Expose port 9000 and start php-fpm server
    EXPOSE 9000
    CMD ["php-fpm"]

here I use php7.2 and then I add some important extensions with composer and NodeJS and NPM to compile the assets from the same image. of course, you can move that part to another NodeJS image but I prefer to keep it there as it works perfectly for me.

MySQL

mysql has a very easy setup

    db:
        image: mysql:5.7
        ports:
          - "3388:3306"
        volumes:
          - ./docker-volumes-data/db:/var/lib/database
        environment:
          MYSQL_DATABASE: laravel_db
          MYSQL_ROOT_PASSWORD: root

I expose the port to 3388 so I can connect to it using any mysql-client like TablePlus or SequelPro

Also, I use volumes to sync the data between the host and the container so I don't lose the data if it restarts or stops

Redis

    redis:
        image: redis
        volumes:
          - ./docker-volumes-data/redis:/data

I use volumes to sync the data too but as you see I don't expose any ports as I use the terminal to check Redis.

laravel queue:listen command is enough for me to monitor the jobs during development

Run docker build and up

Now it's time to start our docker setup and then initialize our project

    $ docker-compose up --build

I run this command every time to build the PHP docker image and run the containers. it will take some time to install everything and the end result should be like:

    Successfully built b98842b1e665
    Successfully tagged docker-demo_app:latest
    Creating docker-demo_db_1        ... done
    Creating docker-demo_app_1       ... done
    Creating docker-demo_webserver_1 ... done
    Creating docker-demo_redis_1     ... done

Now we have every thing ready let's move to the next step

Initialize the project

If you are running a new laravel project then we will run the composer from the app docker container to install the new project. first, we need to get the container name. you can get it from the output of the previous command or just run

    $ docker-compose ps
    
    # output #
    
             Name                        Command               State                 Ports
    ----------------------------------------------------------------------------------------------------
    docker-demo_app_1         docker-php-entrypoint php-fpm    Up      0.0.0.0:2222->22/tcp, 9000/tcp
    docker-demo_db_1          docker-entrypoint.sh mysqld      Up      0.0.0.0:3388->3306/tcp, 33060/tcp
    docker-demo_redis_1       docker-entrypoint.sh redis ...   Up      6379/tcp
    docker-demo_webserver_1   nginx -g daemon off;             Up      0.0.0.0:8080->80/tcp

Then let's run

    $ docker exec -it docker-demo_app_1 composer create-project laravel/laravel code

it'll install laravel project inside /code directory

Basic laravel stuff

we need to run few commands to get the app ready

Key generate

    $ docker exec -it docker-demo_app_1 php artisan key:generate
    
    # output #
    Application key set successfully.

Modify .env file

we just need to change the database and redis to make them work for our app

    # database config
    DB_CONNECTION=mysql
    DB_HOST=db
    DB_PORT=3306
    DB_DATABASE=laravel_db
    DB_USERNAME=root
    DB_PASSWORD=root
    
    # redis config
    REDIS_HOST=redis
    REDIS_PASSWORD=null
    REDIS_PORT=6379

Let's run the migration files to make sure the database works well

    $ docker exec -it docker-demo_app_1 php artisan migrate
    
    # output #
    Migration table created successfully.
    Migrating: 2014_10_12_000000_create_users_table
    Migrated:  2014_10_12_000000_create_users_table (0.05 seconds)
    Migrating: 2014_10_12_100000_create_password_resets_table
    Migrated:  2014_10_12_100000_create_password_resets_table (0.02 seconds)
    Migrating: 2019_08_19_000000_create_failed_jobs_table
    Migrated:  2019_08_19_000000_create_failed_jobs_table (0.01 seconds)

Run tests

    $ docker exec -it docker-demo_app_1 ./vendor/bin/phpunit
    PHPUnit 8.5.0 by Sebastian Bergmann and contributors.

    ..                                                                  2 / 2 (100%)
    
    Time: 2.1 seconds, Memory: 16.00 MB
    
    OK (2 tests, 2 assertions)

Run on browser

just visit http://localhost:8080/ to see the app running

Source code

here are the full files on Github where you can clone and use them

https://github.com/ahmedash95/laravel-docker-template

Conclusions

I hope the article helped you to easily create a docker setup for your app. even make it work with the existing apps shouldn't be a problem at all.

I still see it's not smooth enough. but I hope to find a better way to make it simpler. you can help by sharing ideas or any experience you had with a similar case.

I know there is laradock but I feel it's too big to use it and just have 4 files make it easy to get it running.

at

18 Sep 2019

Want to be updated with Laravel and other fun related stuff I'm doing. Just Subscribe