This is a quick Laravel 8.x project setup tutorial using docker.
The goal is to set up a Laravel project without having to install external dependencies on your machine.
Why Laravel?
When designing a web application, you can use a range of software and frameworks. We assume, however, that Laravel is the best framework for creating modern, full-stack web applications.
Step 1 — Install composer
Get the latest composer release from here: https://getcomposer.org/download/
Step 2 — Create the Laravel project using composer
Open a terminal and write
$ composer create-project laravel/laravel laravel_docker
A project will be created in the laravel_docker directory.
Step 3 — Setup docker
Create a new file Dockerfile
$ nano Dockerfile
FROM ubuntu:xenial
RUN export TERM=linux
ENV HOME /root
ENV DEBIAN_FRONTEND noninteractive
ENV TERM linux
RUN apt-get update && \
apt-get install -y software-properties-common python-software-properties && \
LC_ALL=C.UTF-8 add-apt-repository -y -u ppa:ondrej/php && \
apt-get update -y && apt-get install -y \
apt-transport-https\
netcat\
nginx\
php7.3-bz2\
php7.3-curl\
php7.3-gd\
php7.3-intl\
php7.3-mbstring\
php7.3-mysqli\
php7.3-sqlite\
php7.3-xml\
php7.3-fpm\
php7.3\
--no-install-recommends
COPY --chown=www-data:www-data . /opt/xyz/webapp/
WORKDIR /opt/xyz/webapp
COPY .docker/nginx.conf /etc/nginx/nginx.conf
RUN chown -R www-data:www-data /opt/xyz/webapp/storage
RUN touch storage/logs/laravel.log
RUN chown -R www-data:www-data
RUN chown -R www-data:www-data /opt/xyz/webapp/public
RUN chmod 755 .docker/start.sh
RUN chmod 755 .docker/start_db.sh
RUN ln -s /opt/xyz/webapp/storage/app/public /opt/xyz/webapp/public/storage
RUN chown -R www-data:www-data /opt/xyz/webapp/storage/app/public
RUN crontab .docker/crontab
CMD [".docker/start.sh"]
EXPOSE 80
Create a new directory .docker in your project root
In the .docker directory create a new bash file start.sh
$ nano start.sh
#!/bin/bash
#set -x
pid=0
# SIGUSR1-handler
my_handler() {
echo "my_handler"
}
# SIGTERM-handler
term_handler() {
if [ $pid -ne 0 ]; then
echo "SIGTERM $pid"
kill -SIGTERM "$pid"
wait "$pid"
fi
exit 143; # 128 + 15 -- SIGTERM
}
# setup handlers
# on callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handler
trap 'kill ${!}; my_handler' SIGUSR1
trap 'kill ${!}; term_handler' SIGTERM
# run application
echo configuring nginx server_name: ${NGINX_SRV_NAME}
sed -i "s|NGINX_SRV_NAME|${NGINX_SRV_NAME}|g" /etc/nginx/nginx.conf
echo configuring nginx root: ${NGINX_ROOT}
sed -i "s|NGINX_ROOT|${NGINX_ROOT}|g" /etc/nginx/nginx.conf
cp .env.example .env
grep -o '#[A-Z_]\{1,\}' .env | while read -r line ; do value="" ; eval "value=\$${line:1}" ; echo Found "$line"; sed -i "s|$line|${value}|g" .env ; done
.docker/start_db.sh ${DB_HOST}:${DB_PORT} -t 60 || exit
php artisan cache:clear
php artisan key:generate
php artisan migrate --force
service php7.3-fpm start
nginx &
pid="$!"
# Create and start reading logging files laravel will append logs to these files
touch storage/logs/laravel.log
touch storage/logs/worker.log
tail -f storage/logs/* &
# wait forever
while true
do
tail -f /dev/null & wait ${!}
doneThis will start our application when starting the docker container and will be our entry point for the docker container.
Create a new bash file start-db.sh
$ nano start_db.sh
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
cmdname=$(basename $0)
echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Dont output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $TIMEOUT -gt 0 ]]; then
echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT"
else
echoerr "$cmdname: waiting for $HOST:$PORT without a timeout"
fi
start_ts=$(date +%s)
while :
do
(echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1
result=$?
if [[ $result -eq 0 ]]; then
end_ts=$(date +%s)
echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds"
break
fi
sleep 1
done
return $result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $QUIET -eq 1 ]]; then
timeout $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
else
timeout $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT &
fi
PID=$!
trap "kill -INT -$PID" INT
wait $PID
RESULT=$?
if [[ $RESULT -ne 0 ]]; then
echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT"
fi
return $RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
hostport=(${1//:/ })
HOST=${hostport[0]}
PORT=${hostport[1]}
shift 1
;;
--child)
CHILD=1
shift 1
;;
-q | --quiet)
QUIET=1
shift 1
;;
-s | --strict)
STRICT=1
shift 1
;;
-h)
HOST="$2"
if [[ $HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
HOST="${1#*=}"
shift 1
;;
-p)
PORT="$2"
if [[ $PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
PORT="${1#*=}"
shift 1
;;
-t)
TIMEOUT="$2"
if [[ $TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
CLI="$@"
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$HOST" == "" || "$PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
TIMEOUT=${TIMEOUT:-15}
STRICT=${STRICT:-0}
CHILD=${CHILD:-0}
QUIET=${QUIET:-0}
if [[ $CHILD -gt 0 ]]; then
wait_for
RESULT=$?
exit $RESULT
else
if [[ $TIMEOUT -gt 0 ]]; then
wait_for_wrapper
RESULT=$?
else
wait_for
RESULT=$?
fi
fi
if [[ $CLI != "" ]]; then
if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then
echoerr "$cmdname: strict mode, refusing to execute subprocess"
exit $RESULT
fi
exec $CLI
else
exit $RESULT
fi
This will start the database.
Next, in the .docker directory create a file nginx.conf
$nano nginx.conf
daemon off;
user www-data;
worker_processes auto;
pid /var/run/nginx.pid;
events {
worker_connections 80960;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format custom_format 'REQ:$request ST:$status RT:$request_time FWD:$http_x_forwarded_for';
server_tokens off;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
tcp_nopush on;
tcp_nodelay on;
sendfile on;
keepalive_timeout 500s;
keepalive_requests 100000;
reset_timedout_connection on;
client_body_timeout 100;
client_max_body_size 20M;
send_timeout 500s;
gzip on;
gzip_types text/plain text/css text/xml image/svg+xml application/javascript text/javascript application/x-javascript application/xml;
gzip_disable "MSIE [1-6]\.";
map $server_name $http_x_forwarded_proto2 {
default $http_x_forwarded_proto;
localhost "https";
}
map $server_name $fastcgi_https {
default "on";
localhost "off";
}
server {
listen 8080;
server_name NGINX_SRV_NAME;
root NGINX_ROOT/public;
index index.php index.html;
# error_log /var/log/nginx/webapp-error.log warn;
# access_log /var/log/nginx/webapp-access.log custom_format buffer=16k flush=9s;
error_log stderr warn;
access_log /dev/stdout custom_format buffer=16k flush=9s;
#browser caching of static assets
location ~* \.(css|js|ico|gif|jpeg|jpg|webp|png|svg|eot|otf|woff|woff2|ttf|ogg)$ {
expires max;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
#if ($http_x_forwarded_proto2 != "https"){
#rewrite ^(.*)$ https://$server_name$1 permanent;
#}
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php7.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param HTTPS $fastcgi_https;
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
fastcgi_connect_timeout 600s;
fastcgi_send_timeout 600s;
fastcgi_read_timeout 600s;
}
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;";
}
}
Finally, create docker-compose.yml in the project root.
$ cd .. && nano docker-compose.yml
version: '3'
services:
app:
ports:
- 8080:8080
image: local
container_name: app
restart: unless-stopped
build:
context: .
dockerfile: Dockerfile
links:
- "mysql"
environment:
APP_DEBUG: 0
APP_NAME: Laravel
APP_ENV: local
DB_CONNECTION: mysql
DB_DATABASE: db
DB_HOST: mysql
DB_PASSWORD: password
DB_PORT: 3306
DB_USERNAME: root
LOG_CHANNEL: stack
CACHE_DRIVER: file
QUEUE_CONNECTION: sync
SESSION_DRIVER: file
SESSION_LIFETIME: 120
NGINX_ROOT: /opt/xyz/webapp
NGINX_SRV_NAME: localhost
volumes:
- ./:/opt/xyz/webapp
mysql:
image: mysql:5.7.22
container_name: mysql
restart: unless-stopped
tty: true
ports:
- "3306:3306"
volumes:
- ./data:/var/lib/mysql
environment:
MYSQL_DATABASE: db
MYSQL_ROOT_PASSWORD: password
Finally you project structure should look like this:
This file starts a new container running on a Linux server using nginx with a dabase “db” running on localhost:3306. It will write the environment variables from the docker-compose.yml environment section.
Step 4 — Open the container
$ docker-compose up
This will install all dependencies and start the container and also the database and run on localhost:8080.
Step 5 — The result
If you want to see the docker containers type
$ docker ps
You should see:
To see the app open a browser and go to localhost:8080.
You should see this:
Enjoy coding!