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 toindex.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 (
unzip,libzip-dev), and installs PHP extensions (pdo_mysqlandzip).
# Enable Apache modules
RUN a2enmod rewrite
- Enables the Apache
rewritemodule, 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 installto install PHP dependencies specified in thecomposer.jsonfile.
# 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 themod_rewritemodule is available before applying the rules.Options +FollowSymLinks: Enables theFollowSymLinksoption, which allows the use of symbolic links in the requested URLs.RewriteEngine On: Enables themod_rewriteengine.- 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} !-dandRewriteCond %{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 (/) topublic/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./backenddirectory. It exposes port80inside the container and maps it to port8082on the host. It depends on themysqlservice.
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./frontenddirectory. It exposes port80inside the container and maps it to port8083on 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 port3306on the host and mounts a volume from the./datadirectory 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 themysqlservice and exposed on port8081. Environment variables are set to configure the connection to the MySQL database.
volumes:
data:
volumes: Defines a named volume calleddata, which is used by themysqlservice 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.
