Run Laravel backend, Angular frontend, and MySQL database with Docker Compose.

In this tutorial article, we are going to setup our already existing full-stack Laravel and Angular application; the backend, the frontend and the database, in a way that it going to be portable, easy to deploy on any server or development environment.

1 — The Front End

We need to have a Dockerfile in thefrontend project directory:

# Use the official Node.js image as the base image
FROM node:14.17-alpine as build

# Set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json to leverage Docker cache
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application files
COPY . .

# Build the Angular app for production
RUN npm run build --prod

# Use a lightweight base image for serving the Angular app
FROM nginx:1.21-alpine

# Copy the built app from the build stage to the nginx public directory
COPY --from=build /app/dist/client/ /usr/share/nginx/html

COPY nginx.conf /etc/nginx/nginx.conf

# Expose port 80 for the web server
EXPOSE 80

# Start Nginx
CMD ["nginx", "-g", "daemon off;"]

Note: /app/dist/client/ should be /app/dist/{your-app-name}/

This Dockerfile is used to build a Docker image for serving an Angular application using Node.js for the build stage and Nginx for the production stage. Here’s a breakdown of the Dockerfile:

Base Image: It uses the official Node.js image as the base image with the tag 14.17-alpine. This image is based on Alpine Linux, making it lightweight.

  • FROM node:14.17-alpine as build

Working Directory: Sets the working directory inside the container to /app.

  • WORKDIR /app

Copy Package Files: Copies package.json and package-lock.json to the working directory to leverage Docker cache for faster builds.

  • COPY package*.json ./

Install Dependencies: Runs npm install to install the dependencies specified in the package.json file.

  • RUN npm install

Copy Application Files: Copies the rest of the application files into the working directory.

  • COPY . .

Build Angular App: Runs npm run build --prod to build the Angular app for production.

  • RUN npm run build --prod

Production Stage: Starts a new stage using a lightweight Nginx image.

  • FROM nginx:1.21-alpine

Copy Built App: Copies the built Angular app from the previous build stage into the Nginx public directory (/usr/share/nginx/html).

  • COPY --from=build /app/dist/client/ /usr/share/nginx/html

Copy Nginx Configuration: Copies a custom Nginx configuration file (nginx.conf) to the appropriate location inside the container.

  • COPY nginx.conf /etc/nginx/nginx.conf

Expose Port 80: Exposes port 80 for the web server.

  • EXPOSE 80

Start Nginx: Sets the CMD instruction to start Nginx with the specified configuration.

  • CMD ["nginx", "-g", "daemon off;"]

This Dockerfile is suitable for building a Docker image to deploy an Angular application with Nginx as the web server. It ensures that the production image is as lightweight as possible by using Alpine Linux and separates the build and production stages for better efficiency.

The provided Nginx configuration (nginx.conf) is a simple configuration for serving an Angular application. Here’s a breakdown of the configuration:

events {}
http {
include /etc/nginx/mime.types; server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html; location / {
try_files $uri $uri/ /index.html;
}
}
}
  • events {}: This block is typically used to configure connection processing settings. In this case, it’s empty, meaning it uses default settings.
  • http {}: This block contains the main configuration settings for the HTTP server.
  • include /etc/nginx/mime.types;: This line includes the MIME types configuration file.
  • server {}: This block defines the configuration for the HTTP server.
  • listen 80;: Configures the server to listen on port 80.
  • server_name localhost;: Sets the server name to “localhost.”
  • root /usr/share/nginx/html;: Defines the root directory where the Angular application files are located.
  • index index.html;: Specifies the default file to serve when a directory is requested.
  • location / {}: This block defines configuration for the root location (“/”).
  • try_files $uri $uri/ /index.html;: Configures Nginx to try serving existing files, then directories, and if neither is found, it routes the request to index.html. This is a common configuration for Angular applications that use client-side routing.

Note: Our frontend app is expecting to connect to the backend app on port 8082, We will see how to set that up later.

Note: Our front end was tested locally with node:14.17, choose the version that fits your project

2 — The Back End

1- We need to have a Dockerfile in thebackend project directory:

# Use the official PHP image with Apache
FROM php:7.4.4-apache

# Install necessary extensions and tools
RUN apt-get update \
&& apt-get install -y unzip libzip-dev \
&& docker-php-ext-install pdo_mysql zip

# Enable Apache modules
RUN a2enmod rewrite

# Set the working directory
WORKDIR /var/www/html

# Copy project code

COPY . /var/www/html

RUN chown -R www-data:www-data /var/www/html/

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

RUN composer install

# Set permissions
RUN chown -R www-data:www-data /var/www/html/

# Expose port 80
EXPOSE 80

# Start Apache
CMD ["apache2-foreground"]

This Dockerfile is designed to create a Docker image for a PHP application running on Apache. Here’s a breakdown of the different sections:

# Use the official PHP image with Apache
FROM php:7.4.4-apache
  • Specifies the base image as the official PHP image with Apache, using the tag 7.4.4-apache.
# Install necessary extensions and tools
RUN apt-get update \
&& apt-get install -y unzip libzip-dev \
&& docker-php-ext-install pdo_mysql zip
  • Updates the package list, installs the necessary dependencies (unziplibzip-dev), and installs PHP extensions (pdo_mysql and zip).
# Enable Apache modules
RUN a2enmod rewrite
  • Enables the Apache rewrite module, which is commonly used for URL rewriting.
# Set the working directory
WORKDIR /var/www/html
  • Sets the working directory inside the container to /var/www/html.
# Copy project code
COPY . /var/www/html
  • Copies the project code into the container’s working directory.
RUN chown -R www-data:www-data /var/www/html/
  • Sets ownership of the project files to the Apache user and group (www-data).
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  • Downloads and installs Composer globally in the container.
RUN composer install
  • Runs composer install to install PHP dependencies specified in the composer.json file.
# Set permissions
RUN chown -R www-data:www-data /var/www/html/
  • Sets ownership of the project files to the Apache user and group (www-data).
# Expose port 80
EXPOSE 80
  • Exposes port 80 for incoming HTTP traffic.
# Start Apache
CMD ["apache2-foreground"]
  • Sets the command to start Apache when the container is run.

This Dockerfile is suitable for Laravel applications using Apache as the web server. It installs necessary dependencies, sets up the environment, and configures Apache with the required modules for typical PHP web applications.

2- The following .htaccess file has to be created in thebackend project directory.

This Apache configuration file is designed to work with the mod_rewrite module and is commonly used for routing in PHP applications. Let’s break down the important parts:

<IfModule mod_rewrite.c>
Options +FollowSymLinks
RewriteEngine On
# Uncomment the following lines if you want to force HTTPS
#RewriteCond %{HTTPS} off
#RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Exclude the 'public' directory from rewriting
RewriteCond %{REQUEST_URI} !^/public/

# Check if the requested URL is not a directory and not a file
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f

# Rewrite all other requests to the 'public' directory
RewriteRule ^(.*)$ /public/$1

# Rewrite the root URL to 'public/index.php'
RewriteRule ^(/)?$ public/index.php [L]

</IfModule>
  • <IfModule mod_rewrite.c>: This block checks if the mod_rewrite module is available before applying the rules.
  • Options +FollowSymLinks: Enables the FollowSymLinks option, which allows the use of symbolic links in the requested URLs.
  • RewriteEngine On: Enables the mod_rewrite engine.
  • Commented block for forcing HTTPS: This block is currently commented out, but if uncommented, it would force the use of HTTPS.
  • RewriteCond %{REQUEST_URI} !^/public/: Excludes URLs starting with /public/ from further rewriting.
  • RewriteCond %{REQUEST_FILENAME} !-d and RewriteCond %{REQUEST_FILENAME} !-f: Check if the requested URL is not a directory and not a file.
  • RewriteRule ^(.*)$ /public/$1: Redirects all other requests to the /public/ directory.
  • RewriteRule ^(/)?$ public/index.php [L]: Rewrites the root URL (/) to public/index.php.

3- The .env file

We expect our app to use the docker running MySQL service that we will setup later using the following environment variables:

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=laravel_secret

Note: Our backend was tested locally with php 7.4.4, choose the version that fits your project.

4- The .dockerignore file

.git
node_modules
vendor

3 — The Services

Outside the frontend and the backend directories, we need the following docker-compose file.

This is a docker-compose.yml file that defines a multi-container Docker application with services for a MySQL database, a backend application, an Angular client application, and a PhpMyAdmin service. Let’s break down the key components:

version: '3'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
image: backend
ports:
- "8082:80"
depends_on:
- mysql
  • backend: This service defines a custom build for the Laravel backend application using a Dockerfile located in the ./backend directory. It exposes port 80 inside the container and maps it to port 8082 on the host. It depends on the mysql service.
client:
build:
context: ./frontend
dockerfile: Dockerfile
image: frontend
ports:
- "8083:80"
  • client: This service defines a custom build for the Angular client application using a Dockerfile located in the ./frontend directory. It exposes port 80 inside the container and maps it to port 8083 on the host.
mysql:
image: mysql:5.7
environment:
MYSQL_DATABASE: laravel_db
MYSQL_USER: laravel_user
MYSQL_PASSWORD: laravel_secret
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
volumes:
- ./data:/var/lib/mysql
  • mysql: This service uses the official MySQL 5.7 image. It sets environment variables for database configuration (remember the .env file?), including database name, user, password, and root password. It exposes MySQL port 3306 on the host and mounts a volume from the ./data directory for persistent storage.
# Phpmyadmin service
phpmyadmin:
image: phpmyadmin/phpmyadmin
links:
- mysql
ports:
- "8081:80"
environment:
PMA_HOST: mysql
MYSQL_ROOT_PASSWORD: root
  • phpmyadmin: This service uses the PhpMyAdmin image to provide a web-based interface for MySQL database management. It is linked to the mysql service and exposed on port 8081. Environment variables are set to configure the connection to the MySQL database.
volumes:
data:
  • volumes: Defines a named volume called data, which is used by the mysql service for persistent storage.

This docker-compose.yml file allows you to run the entire application stack with a single command using Docker Compose. It simplifies the management of multiple containers and their dependencies.

Full file:

version: '3'

services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
image: backend
ports:
- "8082:80"
depends_on:
- mysql
client:
build:
context: ./frontend
dockerfile: Dockerfile
image: client
ports:
- "8083:80"
mysql:
image: mysql:5.7
environment:
MYSQL_DATABASE: laravel_db
MYSQL_USER: laravel_user
MYSQL_PASSWORD: laravel_secret
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
volumes:
- ./data:/var/lib/mysql
# Phpmyadmin service
tasks-phpmyadmin:
image: phpmyadmin/phpmyadmin
links:
- mysql
ports:
- "8081:80"
environment:
PMA_HOST: mysql
MYSQL_ROOT_PASSWORD: root
volumes:
data:

To build the images and run the containers:

docker-compose up -d

To setup the back-end project we need to execute to following commands:

Migrate the Database:

docker-compose exec backend php artisan migrate:fresh

This command runs Laravel Artisan’s migrate:fresh command, which rolls back all database migrations and then migrates the database afresh. It’s typically used for setting up the database schema.

Seed the Database:

docker-compose exec backend php artisan db:seed

This command runs Laravel Artisan’s db:seed command, which populates the database with seed data. Seeders are used to fill database tables with sample or default data.

Generate Application Key:

docker-compose exec backend php artisan key:generate

This command generates a new application key for Laravel. The application key is used for encrypting various elements of the application, such as session data and encrypted cookies.

Create Symbolic Link for Storage:

docker-compose exec backend php artisan storage:link

This command creates a symbolic link from the public/storage directory to the storage/app/public directory. It’s used to make files in the storage directory accessible from the web.

Docker Compose and Dockerizing applications offer several benefits, making development, deployment, and maintenance processes more efficient and consistent. Here are some key advantages:

1. Isolation and Portability:

  • Docker Containers: Docker containers encapsulate an application and its dependencies, ensuring that the application runs consistently across different environments, regardless of the underlying host system.
  • Portability: Dockerized applications can be easily moved between development, testing, and production environments, reducing the “it works on my machine” problem.

2. Consistent Development Environments:

  • Reproducibility: Developers can use the same Dockerized environment, eliminating the “it works on my machine” issue and ensuring consistent development experiences across the team.
  • Dependency Management: Dependencies and configuration are defined in the Dockerfile and can be version-controlled, ensuring that everyone uses the same versions of libraries and tools.

3. Ease of Deployment:

  • Deployment Consistency: Docker Compose allows you to define a multi-container application in a single file, ensuring that the entire stack is deployed consistently. This simplifies the deployment process and reduces the risk of deployment-related issues.

4. Efficient Resource Utilization:

  • Resource Isolation: Docker containers isolate applications and their dependencies, preventing conflicts with other applications running on the same host. This leads to more efficient resource utilization and avoids interference between applications.

5. Scalability:

  • Container Orchestration: Docker containers can be easily orchestrated and scaled using tools like Docker Swarm or Kubernetes. This makes it straightforward to scale applications horizontally, handling increased loads by adding more containers.

6. Simplified Dependency Management:

  • Containerization: Packaging an application and its dependencies into a container simplifies dependency management. Developers don’t need to worry about installing specific versions of libraries on their local machines.

7. Security:

  • Isolation: Containers provide a level of isolation between applications and the host system, reducing the impact of security vulnerabilities.
  • Image Signing: Docker supports image signing, ensuring that only signed and verified images are used in the production environment.

8. Collaboration:

  • Standardization: Dockerization standardizes the way applications are packaged and deployed. This standardization facilitates collaboration among development, operations, and other teams involved in the software development lifecycle.

9. Resource Efficiency:

  • Lightweight Containers: Docker containers are lightweight compared to traditional virtual machines, leading to faster startup times and efficient resource utilization.

10. Facilitates Microservices Architecture:

  • Service Composition: Docker Compose enables the definition of multi-container applications, making it easier to design and deploy applications based on microservices architecture.

11. Infrastructure as Code (IaC):

  • Configuration as Code: Docker Compose files serve as a form of Infrastructure as Code, allowing developers and operations teams to manage the entire application stack using version-controlled configuration files.

In summary, Docker Compose and Dockerizing applications offer a standardized, portable, and efficient way to manage the development, deployment, and scaling of applications, leading to improved collaboration, consistency, and ease of management across the software development lifecycle.

Leave a Comment

Your email address will not be published. Required fields are marked *