Skip to content

Instantly share code, notes, and snippets.

@thoriqmacto
Last active May 19, 2024 11:47
Show Gist options
  • Save thoriqmacto/5726e09745d1e8f6a955fef12a35dd87 to your computer and use it in GitHub Desktop.
Save thoriqmacto/5726e09745d1e8f6a955fef12a35dd87 to your computer and use it in GitHub Desktop.
[Docker] Setup Docker Environment for Fresh Laravel Web Project

Courtesy

This notes is simplify version of long series of Docker Setup for local web development project write by tech.osteel.me. Adjustments were made based on my habit and repetitive step when starting new laravel new project.

Preparation

  1. Ensure Docker Desktop with Docker Compose has been installed.
  2. Create project folder (for example: /Users/macto/Development/laravel-project).
  3. Change project name inside this notes with yours.
    • For folder: laravel-project.
    • For inside config files: laravelproject.
  4. In this notes I use only below services:
    • laravel (as backend)
    • mysql (as database)
    • phpmyadmin (as database management ui)

Folder Structure

final folder structure for this setup as per below:

laravel-project/
├── .docker/
│   ├── mysql/
│   │   └── my.cnf
│   └── nginx/
│       └── conf.d/
│           └── backend.conf
│           └── phpmyadmin.conf
├── src/
│   └── backend/
│       └── Dockerfile
├── .env
├── .env.example
├── .gitignore
└── docker-compose.yml

Create docker-compose.yml

  • Copy, paste and save as docker-compose.yml for below minimal configuration:
version: '3.8'

# Services
services:

  # Nginx Service
  nginx:
    build: ./.docker/nginx
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./src/backend:/var/www/backend
      - ./.docker/nginx/conf.d:/etc/nginx/conf.d
      - phpmyadmindata:/var/www/phpmyadmin
      - ./.docker/nginx/certs:/etc/nginx/certs
    depends_on:
      - backend
      - phpmyadmin

  # Backend Service
  backend:
    build:
      context: ./src/backend
      target: backend
    working_dir: /var/www/backend
    volumes:
      - ./src/bend:/var/www/backend
      - ./.docker/backend/init:/opt/files/init
      - ./.docker/nginx/certs:/usr/local/share/ca-certificates
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started

  # MySQL Service
  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: laravelproject
    volumes:
      - ./.docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - mysqldata:/var/lib/mysql
    healthcheck:
      test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD
      interval: 5s
      retries: 10

  # PhpMyAdmin Service
  phpmyadmin:
    image: phpmyadmin/phpmyadmin:5-fpm-alpine
    environment:
      PMA_HOST: mysql
    volumes:
      - phpmyadmindata:/var/www/html
    depends_on:
      mysql:
        condition: service_healthy

# Volumes
volumes:

  mysqldata:

  phpmyadmindata:

Setup Nginx Dockerfile

  • create Dockerfile file in .docker/nginx/
  • copy below code to new created Dockerfile
FROM nginx:1.19-alpine

# Install packages
RUN apk --update --no-cache add openssl

Setup Backend Dockerfile

  • create backend folder in laravel-project/src/
  • create Dockerfile file in laravel-project/src/backend/
  • copy below code:
FROM php:8.0-fpm-alpine as backend

# Import extension installer
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/

# Install extensions
RUN install-php-extensions pdo_mysql bcmath opcache redis

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

# Configure PHP
COPY .docker/php.ini $PHP_INI_DIR/conf.d/opcache.ini

# Use the default development configuration
RUN mv $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini

# Install extra packages
RUN apk --no-cache add bash mysql-client mariadb-connector-c-dev

FROM backend as worker

# Start worker
CMD ["php", "/var/www/bend/artisan", "queue:work"]

Setup php.ini

  • create .docker folder in laravel-project/src/backend/
  • create php.ini file inside newly created .docker folder
  • copy below code to the php.ini:
[opcache]
opcache.enable=1
opcache.revalidate_freq=0
opcache.validate_timestamps=1
opcache.max_accelerated_files=10000
opcache.memory_consumption=192
opcache.max_wasted_percentage=10
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1

Nginx Config for Backend Service (Laravel)

  • create backend.conf file in .docker/nginx/conf.d
  • copy below code:
server {
    listen      443 ssl http2;
    listen      [::]:443 ssl http2;
    server_name backend.laravelproject.test *.ngrok.io;
    root        /var/www/backend/public;

    ssl_certificate     /etc/nginx/certs/laravelproject.test.crt;
    ssl_certificate_key /etc/nginx/certs/laravelproject.test.key;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

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

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass  bend:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include       fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

server {
    listen      80;
    listen      [::]:80;
    server_name backend.laravelproject.test;
    return      301 https://$server_name$request_uri;
}

Nginx Config for phpmyadmin

  • create phpmyadmin.conf file in .docker/nginx/conf.d
  • copy below code:
server {
    listen      443 ssl http2;
    listen      [::]:443 ssl http2;
    server_name phpmyadmin.laravelproject.test;
    root        /var/www/phpmyadmin;
    index       index.php;

    ssl_certificate     /etc/nginx/certs/laravelproject.test.crt;
    ssl_certificate_key /etc/nginx/certs/laravelproject.test.key;
    location ~* \.php$ {
        fastcgi_pass   phpmyadmin:9000;
        root           /var/www/html;
        include        fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param  SCRIPT_NAME     $fastcgi_script_name;
    }
}

server {
    listen      80;
    listen      [::]:80;
    server_name phpmyadmin.laravelproject.test;
    return      301 https://$server_name$request_uri;
}

MySQL Config

  • create mysql folder in laravel-project/.docker
  • create my.cnf file in newly created mysql folder
  • copy below code to my.cnf file
[mysqld]
collation-server     = utf8mb4_unicode_ci
character-set-server = utf8mb4
default-authentication-plugin = mysql_native_password

Copy .env.example to .env

  • do this in root folder
  • do the same in src/backend

Create bash init to automate process

  • create init file in laravel-project/.docker/backend
  • copy below code:
#!/bin/bash

# Install Composer dependencies
composer install -d "/var/www/backend"

# Deal with the .env file if necessary
if [ ! -f "/var/www/backend/.env" ]; then
    # Create .env file
    cat > "/var/www/backend/.env" << EOF
APP_NAME=laravelproject
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://backend.laravelproject.test

LOG_CHANNEL=single

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravelproject
DB_USERNAME=root
DB_PASSWORD=root

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=redis
REDIS_HOST=redis
SESSION_DRIVER=file
EOF

    # Generate application key
    php "/var/www/backend/artisan" key:generate --ansi
fi

# Database
php "/var/www/backend/artisan" migrate
  • update init permission
$ chmod +x .docker/backend/init

Create bash script for automating or simplify commands

  • create laravelproject.sh in laravel-project root folder
  • change permission $ chmod +x laravelproject.sh
  • copy below code (to check and edit any github account related setup):
#!/bin/bash

#######################################
# FUNCTIONS
#######################################

# Run an Artisan command
artisan () {
    docker-compose run --rm bend php artisan "${@:1}"
}

# Build all of the images or the specified one
build () {
    docker-compose build "${@:1}"
}

# Run a Composer command
composer () {
    docker-compose run --rm bend composer "${@:1}"
}

# Remove the entire Docker environment
destroy () {
    read -p "This will delete containers, volumes and images. Are you sure? [y/N]: " -r
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit; fi
    docker-compose down -v --rmi all --remove-orphans
}

# Stop and destroy all containers
down () {
    docker-compose down "${@:1}"
}

# Create and start the containers and volumes
start(){
    docker-compose up -d
}

# Stop the containers
stop () {
    docker-compose stop
}

# Display and tail the logs
logs () {
    docker-compose logs -f "${@:1}"
}

# Restart the containers
restart () {
    stop && start
}

# Run a Yarn command
yarn () {
    docker-compose run --rm frontend yarn "${@:1}"
}

# Create .env from .env.example
env () {
    if [ ! -f .env ]; then
        cp .env.example .env
    fi
}

# Initialise the Docker environment and the application
init () {
    env \
        && down -v \
        && build \
        && docker-compose run --rm --entrypoint="//opt/files/init" backend \
        && yarn install
    
    if [ ! -f .docker/nginx/certs/laravelproject.test.crt ]; then
        cert_generate
    fi

    start && cert_install
}

# Update the Docker environment
update () {
    git pull \
        && build \
        && composer install \
        && artisan migrate \
        && yarn install \
        && start
}

# Clone or update the repositories
repositories () {
    repos=(backend)
    cd src
    for repo in "${repos[@]}";
    do
        git clone "git@github.com:username/${repo}.git" "$repo" || (cd "$repo" ; git pull ; cd ..) || true
    done
    cd ..
}

# Generate a wildcard certificate
cert_generate () {
    rm -Rf .docker/nginx/certs/laravelproject.test.*
    docker-compose run --rm nginx sh -c "cd /etc/nginx/certs && touch openssl.cnf && cat /etc/ssl/openssl.cnf > openssl.cnf && echo \"\" >> openssl.cnf && echo \"[ SAN ]\" >> openssl.cnf && echo \"subjectAltName=DNS.1:laravelproject.test,DNS.2:*.laravelproject.test\" >> openssl.cnf && openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout laravelproject.test.key -out heartweb.test.crt -days 3650 -subj \"/CN=*.laravelproject.test\" -config openssl.cnf -extensions SAN && rm openssl.cnf"
}

cert_install () {
    if [[ "$OSTYPE" == "darwin"* ]]; then
        sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain .docker/nginx/certs/laravelproject.test.crt
    elif [[ "$OSTYPE" == "linux-gnu" ]]; then
        sudo ln -s "$(pwd)/.docker/nginx/certs/laravelproject.test.crt" /usr/local/share/ca-certificates/laravelproject.test.crt
        sudo update-ca-certificates
    else
        echo "Could not install the certificate on the host machine, please do it manually"
    fi

    docker-compose exec bend update-ca-certificates
}

#######################################
# MENU
#######################################
case "$1" in
    artisan)
        artisan "${@:2}"
        ;;
    build)
        build "${@:2}"
        ;;
    composer)
        composer "${@:2}"
        ;;
    destroy)
        destroy
        ;;
    down)
        down "${@:2}"
        ;;
    logs)
        logs "${@:2}"
        ;;
    restart)
        restart
        ;;
    start)
        start
        ;;
    stop)
        stop
        ;;
    yarn)
        yarn "${@:2}"
        ;;
    init)
        init
        ;;
    update)
        update
        ;;
    repositories)
        repositories
        ;;
    cert)
        case "$2" in
            generate)
                cert_generate
                ;;
            install)
                cert_install
                ;;
            *)
                cat << EOF

Certificate management commands.

Usage:
    demo cert <command>

Available commands:
    generate .................................. Generate a new certificate
    install ................................... Install the certificate

EOF
                ;;
        esac
        ;;
    *)
        cat << EOF

Command line interface for the Docker-based web development environment demo.

Usage:
    demo <command> [options] [arguments]

Available commands:
    artisan ................................... Run an Artisan command
    build [image] ............................. Build all of the images or the specified one
    composer .................................. Run a Composer command
    cert ...................................... Certificate management commands
        generate .............................. Generate a new certificate
        install ............................... Install the certificate
    destroy ................................... Remove the entire Docker environment
    down [-v] ................................. Stop and destroy all containers
                                                Options:
                                                    -v .................... Destroy the volumes as well
    init ...................................... Initialise the Docker environment and the application
    logs [container] .......................... Display and tail the logs of all containers or the specified one's
    restart ................................... Restart the containers
    start ..................................... Start the containers
    stop ...................................... Stop the containers
    update .................................... Update the Docker environment
    yarn ...................................... Run a Yarn command

EOF
        exit 1
        ;;
esac
  • edit ~/.bashrc
  • copy below code to last line of .bashrc
function laravelproject {
    cd /PATH/TO/YOUR/PROJECT && bash demo $*
        cd -
}
  • save and run command $ source ~/.bashrc to re-load .bashrc

Run image installation

run below command:

$ docker compose up -d

Update local hosts file to resolve local domain aliases

edit /etc/hosts file with below:

127.0.0.1 backend.laravelproject.test phpmyadmin.laravelproject.test

To check list of images used by application

run below command:

$ docker compose images

To check which containers are currently running

run below command:

$ docker compose ps

To stop containers

run below command:

$ docker compose stop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment