Introduction
To containerize an application refers to the process of adapting an application and its components in order to be able to run it in lightweight environments known as containers.
I am going to use Docker Compose to containerize a Laravel application for development.
- An
app
service running PHP7.4-FPM; - A
db
service running MySQL 5.7; - An
nginx
service that uses theapp
service to parse PHP code before serving the Laravel application to the final user.
Prerequisites
- Linux environment
- Docker
- Docker Compose
Step 1 — Laravel App
composer create-project --prefer-dist laravel/laravel myapi
cd myapi
In the next step, we’ll create a .env
configuration file to set up the application.
Step 2 — Setting Up the Application’s .env
File
cp .env.example .env
Open this file using nano
or your text editor of choice:
nano .env
APP_NAME=myapi
APP_ENV=dev
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=myapi
DB_USERNAME=root
DB_PASSWORD=root
...
Feel free to also change the database name, username, and password, if you wish. These variables will be leveraged in a later step where we’ll set up the docker-compose.yml
file to configure our services.
Step 3 — Setting Up the Application’s Dockerfile
Although both our MySQL and Nginx services will be based on default images obtained from the Docker Hub, we still need to build a custom image for the application container. We’ll create a new Dockerfile for that.
Our myapi image will be based on the php:7.4-fpm
official PHP image from Docker Hub. On top of that basic PHP-FPM environment, we’ll install a few extra PHP modules and the Composer dependency management tool.
Create a new Dockerfile with:
nano Dockerfile
Copy the following contents to your Dockerfile:Dockerfile
FROM php:7.4-fpm
# Arguments defined in docker-compose.yml
ARG user
ARG uid
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Create system user to run Composer and Artisan Commands
RUN useradd -G www-data,root -u $uid -d /home/$user $user
RUN mkdir -p /home/$user/.composer && \
chown -R $user:$user /home/$user
# Set working directory
WORKDIR /var/www
USER $user
Step 4 — Setting Up Nginx Configuration and Database Dump Files
We’ll now set up a folder with files that will be used to configure and initialize our service containers.
To set up Nginx, we’ll share a myapi.conf
the file that will configure how the application is served. Create the docker-compose/nginx
folder with:
mkdir -p docker-compose/nginx
Open a new file named myapi.conf
within that directory:
nano docker-compose/nginx/myapi.conf
Copy the following Nginx configuration to that file:docker-compose/nginx/myapi.conf
server {
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;
}
}
Step 5 — Creating a Multi-Container Environment with Docker Compose
We’ll define three different services in our docker-compose.yml
file: app
, db
, and nginx
.
The app
service will build an image called myapi
, based on the Dockerfile we’ve previously created. The container defined by this service will run a php-fpm
server to parse PHP code and send the results back to the nginx
service, which will be running on a separate container. The mysql
service defines a container running a MySQL 5.7 server. Our services will share a bridge network named myapi
.
The application files will be synchronized on both the app
and the nginx
services via bind mounts. Bind mounts are useful in development environments because they allow for a performant two-way sync between host machine and containers.
Create a new docker-compose.yml
file at the root of the application folder:
nano docker-compose.yml
A typical docker-compose.yml
file starts with a version definition, followed by a services
node, under which all services are defined. Shared networks are usually defined at the bottom of that file.
To get started, copy this boilerplate code into your docker-compose.yml
file:docker-compose.yml
We’ll now edit the services
node to include the app
, db
and nginx
services.
The app
Service
The app
service will set up a container named myapi-app
. It builds a new Docker image based on a Dockerfile located in the same path as the docker-compose.yml
file. The new image will be saved locally under the name myapi
.
Even though the document root being served as the application is located in the nginx
container, we need the application files somewhere inside the app
container as well, so we’re able to execute command line tasks with the Laravel Artisan tool.
Copy the following service definition under your services
node, inside the docker-compose.yml
file:docker-compose.yml
These settings do the following:
version: "3.3"
services:
app:
build:
args:
user: myapi
uid: 1000
context: ./
dockerfile: Dockerfile
image: myapi
container_name: myapi-app
restart: unless-stopped
working_dir: /var/www/
volumes:
- ./:/var/www
networks:
- myapi
db:
image: mysql:5.7
container_name: myapi-db
restart: unless-stopped
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_USER: ${DB_USERNAME}
SERVICE_TAGS: dev
SERVICE_NAME: mysql
volumes:
- ./docker-compose/mysql:/docker-entrypoint-initdb.d
networks:
- myapi
nginx:
image: nginx:alpine
container_name: myapi-nginx
restart: unless-stopped
ports:
- 8000:80
volumes:
- ./:/var/www
- ./docker-compose/nginx:/etc/nginx/conf.d/
networks:
- myapi
networks:
myapi:
driver: bridge
Make sure you save the file when you’re done.
Step 6 — Running the Application with Docker Compose
We’ll now use docker-compose
commands to build the application image and run the services we specified in our setup.
Build the app
image with the following command:
docker-compose build app
This command might take a few minutes to complete. You’ll see output similar to this:
When the build is finished, you can run the environment in background mode with:
docker-compose up -d
OutputCreating myapi-db ... done
Creating myapi-app ... done
Creating myapi-nginx ... done
This will run your containers in the background. To show information about the state of your active services, run:
docker-compose ps
You’ll see output like this:
Your environment is now up and running, but we still need to execute a couple commands to finish setting up the application. You can use the docker-compose exec
command to execute commands in the service containers, such as an ls -l
to show detailed information about files in the application directory:
docker-compose exec app ls -l
We’ll now run composer install
to install the application dependencies:
docker-compose exec app composer install
The last thing we need to do before testing the application is to generate a unique application key with the artisan
Laravel command-line tool. This key is used to encrypt user sessions and other sensitive data:
docker-compose exec app php artisan key:generate
Now go to your browser and access your server’s domain name or IP address on port 8000:
http://server_domain_or_IP:8000
You’ll see a page like this:
