Laravel 7 & Docker Development Setup on Windows

Laravel 7 & Docker Development Setup on Windows

This guide will help you get started with Laravel 7 and Docker on Windows.

To continue ensure you have Docker and Git installed on your windows PC.

Being a windows user,
When you mount volumes in Docker for database use, it does not let you access that volume via the windows host file system, (this applies to volume mounts not bind mounts) as “When running linux based containers on a windows host, the actual volumes will be stored within the linux VM and will not be available on the host’s file system” .
Which becomes an issue when you want to backup your databases. So I prefer to have MySQL, Mongo etc. installed on my host PC, making backups, and data manipulation easier.
I use Laragon for this purpose which starts up with windows and starts my MySQL and Mongo Db’s automatically.

For my development I use the Visual Studio Code IDE.
2 changes to make your life easier are. 1. Install the docker plugin, which will allow you to manage containers and images in your IDE, and set your default terminal to bash.

Create a folder with your desired project name

md laravel-dockerized
cd laravel-dockerized

Lets clone latest Laravel version into current directory

git clone https://github.com/laravel/laravel.git .

Delete the .git folder

DEL .git

Create a copy of .env.example and name it as .env

Now we will create a folder to hold our docker files and docker container configurations , and we will have a docker-compose.yml file which will be used in development to launch our containers. Create the following folders and files

Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

//Folders

docker
docker\nginx
docker\nginx\conf.d
docker\nginx\certs
docker\php

//Files

docker-compose.yml
Dockerfile
docker\php\local.ini
docker\nginx\certs\localhost.crt
docker\nginx\certs\localhost.key
docker\nginx\conf.d\app.conf

Contents for each of the files are below.

Customize container names accordingly in your docker-compose , webpack.mix.js and app.conf (line 12)

Container name can not contain spaces, use slug style names.

docker/nginx/conf.d/app.conf
( This file is the configuration which tells NGINX container to proxy requests to your PHP container. Change line 12 to match your PHP container name)

server {
    listen 80;
    listen 443 ssl;
    ssl_certificate /etc/nginx/certs/localhost.crt;
    ssl_certificate_key /etc/nginx/certs/localhost.key;
    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 php-my-app:9000; #change this to match your app container name
        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;
    }
}

docker/nginx/certs/localhost.crt
(We use self signed certificates for SSL)

-----BEGIN CERTIFICATE-----
MIIDRzCCAi+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlsb2Nh
bGhvc3QwHhcNMjAwMzE0MjIxMDA0WhcNMzAwMzE0MjIxMDA0WjAUMRIwEAYDVQQD
Ewlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaisZY
L9fKlcIFcEvyHdYsMu9eMq9YWeP6aGQTA6/PBnTv9BU4A12XRV/JSHhW9XUcHDRo
E4ny8TdJPGwAuVtw48+FEvggCGmerLHcf7LMfavkPG2dMZvVikIaqiQBdZNtKOnV
JmVi1ZwMqdfcoCozJ2GbfbvE8TTan23QmgjWSjr275n7KAiRIAgYAQYGaGthOy5q
7T7k804ZCN5TmrHyRG55fWkhpY5GODbvfx/AogQxw1KjfrQ5bJH+6J8fzFzPAYri
wKgLIsDr7ITRcioI9JeWJG8yH22tpNYzmWXz7hh06cNgVL0LP3t71kIvxxcFCAqa
1rwMZji+GRPjOWkvAgMBAAGjgaMwgaAwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMC
AvQwOwYDVR0lBDQwMgYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYB
BQUHAwQGCCsGAQUFBwMIMBEGCWCGSAGG+EIBAQQEAwIA9zAUBgNVHREEDTALgAls
b2NhbGhvc3QwHQYDVR0OBBYEFJbCxsIacT7hFUdsiE+1/Y7RTnthMA0GCSqGSIb3
DQEBCwUAA4IBAQCNExAOV1kZLu7JdeKHtWTWSC8A99zAXwQj1AuLy+gE6mDSac2I
D96zHqsXvr/loCJim0WNOuKLtu6Dv6UNSGN7z8IvW45EIg/LaBV9HXshCv9Uex/Q
F9/mKKwem2y8SKwgZBho4J7trtSiYMaNZ5KFCXZ7zhCXYDP0uSf3W2LO7tqYQh3o
mG9LtLUmE5vwi93IWbhvF0iW3VYXj8dLw7jqWcV4B3UpFr9KKbcrLZV0PIfEO480
nQ2cteQ7b6ovL46iTStjx4zCCxCuHL61lRmqzdOc66GtKM+y37ipUaRe1WnZpn51
6ljJ7bGdhc1u0/w/Z49wniY8lnlYiPbP6EMh
-----END CERTIFICATE-----

docker/nginx/certs/localhost.key
(We use self signed certificates for SSL)

-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAmorGWC/XypXCBXBL8h3WLDLvXjKvWFnj+mhkEwOvzwZ07/QV
OANdl0VfyUh4VvV1HBw0aBOJ8vE3STxsALlbcOPPhRL4IAhpnqyx3H+yzH2r5Dxt
nTGb1YpCGqokAXWTbSjp1SZlYtWcDKnX3KAqMydhm327xPE02p9t0JoI1ko69u+Z
+ygIkSAIGAEGBmhrYTsuau0+5PNOGQjeU5qx8kRueX1pIaWORjg2738fwKIEMcNS
o360OWyR/uifH8xczwGK4sCoCyLA6+yE0XIqCPSXliRvMh9traTWM5ll8+4YdOnD
YFS9Cz97e9ZCL8cXBQgKmta8DGY4vhkT4zlpLwIDAQABAoIBADIdNlx6hzuGfLhO
xhHpYv5KNh0RypKX87nMCEeyNlyn91uohwj27m6TbJBVE9D/H87RxpzJWT6Swh7Z
nRxO/zwIY87/a77Xe4ic78BVGKH/TrJgdhs3bxU8FFuZOLvQaNJJJiqcnNwD8Oqa
WQmOVqDn8Fr/1tfyb/VFJdMzYBI81tKj6ponWC0bIXkQqipfiRh6Eg7HLQkxdOUV
tG7vGXINzwod/XH7JpEkJ3LYAeSzRGIRqK7CBtSVPrjcaaXFy4qme+vB89simdpb
71N0bM9IRwvi0cDnsyENVzL1rohuf8ZVnKTKbN9thRDZDOoMx7lV5LpBPf8Z8K+3
qyy1IoECgYEA0r7P1B/bAscjt8PJ0T8TD8cCVIeGSrsCIsDBmW/Se0TSwTZBjPHE
Zzd+eSnmf3339lpEsMJv1oLcsBYrGxWspv9pTIZsL4A88tdBTT8zRVay+WyCkD3o
A9xMM3M58vEv0hAV3aeGR/wN8OAFWIyEyOgkJibJd6/FHn1WYqi5y98CgYEAu7pU
549vr9LoblhVbTaeBnYw0nUw8SwIOWJE8CB/yfYQ3NIiYE6dD/lY7uIUTgCXJyuZ
Sv+37G0IZfSOQ/oiMy32UL1lxgJEbQb2jluDEfG/1MC2w0zCtka3UE43Daep5Ap3
cfSpbqDrnvY7O5vqJ+CwdJ3/AGsueeCtjZFrDLECgYBBQ+aJAvSIf7OIDZJKwwXc
NP17wBzOt+uJZ7iNFBxTIJVEUNEsHRN+A6mfTGKdyR0ppfrv4sdP32cJAVSIJsY5
UZACqv6GF2jIq/EFQzVVm+wBYqFsmj9oMqlGpcCkhB0TKPgclOz5Dg4jMr11Fs9o
iLvpNMnfzwd00cwW8bnYcQKBgGg4aiqkVz0luiSIpeScDd9IfpJYvXMnHum8xMkL
Qea91V7CypLtEgVsSRNWMC+d5Ey6x7/7eACqB5+Sxei3VYVUXws0GuxzFcnxs7AU
3g94Rye2VcDXPMI1QFA1HBtYmdkgdm+thOMGM8lsqjXNuZGqDEyx6oAxAPxo3/FY
ajYBAoGAaOO46iOzk7otEv0aVx+bdVqSXo39oIOO5VFm8Na75iAB6VNiBHNVcMko
DwKxuDy/589EawlnP2kxHoeBk9x/HFMl6Acggg6uxrAKXgZ6VrnWlnYlUKq9MQos
eDQKFE0fOeCWfOleXRGUmDAyOwbW1+BuHcPv2zefk711V01jfxg=
-----END RSA PRIVATE KEY-----

docker/php/local.ini
(Add any php ini settings you would like to overwrite in here)

upload_max_filesize=128M
post_max_size=32M

docker-compose.yml
(if you are app does not have a UI , then you can remove the Node container and access your app on https://localhost . If you want to run a database as a container , you can add it here.

version: '3.1'
services:

  #PHP Service
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: app
    container_name: php-my-app
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: php-app
    working_dir: /var/www
    volumes:
      - ./:/var/www
      - /var/www/vendor #ignores the folder on host
      - ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini      
    networks:
      - app-network

  #Nginx Service For Development Only
  webserver:
    image: nginx:alpine
    container_name: webserver-my-app
    restart: unless-stopped
    tty: true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./:/var/www
      - ./docker/nginx/conf.d/:/etc/nginx/conf.d/
      - ./docker/nginx/certs/:/etc/nginx/certs/
    networks:
      - app-network


  #Node Service For Development Only
  node:
    image: node:10
    container_name: node-my-app
    restart: unless-stopped
    tty: true
    working_dir: /var/www
    volumes:
      - ./:/var/www
      - /var/www/node_modules #ignores the folder on host
    ports: 
      - "9056:9056"
    command: "tail -f /dev/null" #command to keep container running
    networks:
      - app-network

#Docker Networks
networks:
  app-network:
    driver: bridge

Dockerfile
(The PHP container is managed from this file. you will run migrations and artisan commands against this container as explained later)

FROM php:7.2-fpm

# 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 \
  vim \
  unzip \
  git \
  curl

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install extensions
RUN docker-php-ext-install 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

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# 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 . /var/www

# Copy existing application directory permissions
#COPY --chown=www:www . /var/www

#RUN mkdir -p vendor
RUN chown www /var/www

# Change current user to www
USER www

#Install Dependencies 
RUN composer install

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

Edit you Laravel webpack.mix.js file
(Setup browser sync to a specific port that we exposed in our docker-compose.yml file)

let mix = require('laravel-mix');

mix
   .js('resources/js/app.js', 'public/js/app.js')
   .sass('resources/sass/app.scss', 'public/css/app.css')

   .browserSync({
      proxy: 'https://webserver-my-app', //Webserver container name
      port: 9056 //exposed node container port
   });

That’s all the boiler plate you need to get started.

In order to connect to MYSQL or any database on you host PC , in your .env set your DB_HOST=host.docker.internal

Building Your Containers

docker-compose build

Starts Containers

docker-compose up -d

On first run to install node modules

docker exec node-my-app npm install

Once done and subsequent runs (Hot reloading)
Access your app on https://localhost:9056

docker exec node-my-app npm run watch

Compiling Assets for production:
Run all Mix tasks and minify output… 

docker exec node-my-app npm run production

PHP artisan commands can be run in the same manner against your php app container

Code can be found on https://github.com/zaidk26/laravel-dockerized

To get your application deployed to production environment, a great tool to use is Dokku. Dokku helps you build and manage the lifecycle of applications. It will allow you to git push your code to your server, which will automatically re-bulid and and deploy your container with the new changes. Digital Ocean is a great and cost effective VPS provider that has one click installs for Dokku, (Tip. use a VPS with a least 2 GB of Memory) .

Drop a comment if you would like a tutorial on deploying Laravel Application on Dokku.