We are going to deploy a simple 3 Tier Architecture containing a frontend, a backend and a database on Docker.
Docker can be used to create simple containers. But you need Docker Compose to deploy applications that contain multiple containers.
Frontend Container:
Create a frontend folder that has the required html, CSS and JavaScript files.
Next, create a file called docker-compose.yml with the below information.
version: "3"
services:
frontend:
image: httpd:latest
volumes:
- "./frontend:/usr/local/apache2/htdocs"
ports:
- 3000:80
In this file, we are telling docker to create a container using a httpd image with the latest tag and mount the local files in ./frontend to the /usr/local/apache2/htdocs directory inside the container. We are also mapping port 3000 of your local machine to port 80 of the container.
Use docker compose up
to deploy the application and access the frontend application on 'http://localhost:3000' in your browser. Use Ctrl + C to stop the container.
Backend Container:
Create a backend folder that contains the required PHP file. Make sure that the PHP file contains,
header('Access-Control-Allow-Origin: http://localhost:3000');
so that we can call the backend api from the frontend.
Instead of using a pre-existing image, we are going to create an image that contains all our required dependencies.
Create a new file called Dockerfile
that contains the following code.
FROM ubuntu:20.04
LABEL maintainer="testexample@gmail.com"
LABEL description="Apache / PHP development environment"
ARG DEBIAN_FRONTEND=newt
RUN apt-get update && apt-get install -y lsb-release && apt-get clean all
RUN apt install ca-certificates apt-transport-https software-properties-common -y
RUN add-apt-repository ppa:ondrej/php
RUN apt-get -y update && apt-get install -y \
apache2 \
php8.0 \
libapache2-mod-php8.0 \
php8.0-bcmath \
php8.0-gd \
php8.0-sqlite \
php8.0-mysql \
php8.0-curl \
php8.0-xml \
php8.0-mbstring \
php8.0-zip \
mcrypt \
nano
RUN apt-get install locales
RUN locale-gen fr_FR.UTF-8
RUN locale-gen en_US.UTF-8
RUN locale-gen de_DE.UTF-8
# config PHP
# we want a dev server which shows PHP errors
RUN sed -i -e 's/^error_reporting\s*=.*/error_reporting = E_ALL/' /etc/php/8.0/apache2/php.ini
RUN sed -i -e 's/^display_errors\s*=.*/display_errors = On/' /etc/php/8.0/apache2/php.ini
RUN sed -i -e 's/^zlib.output_compression\s*=.*/zlib.output_compression = Off/' /etc/php/8.0/apache2/php.ini
# to be able to use "nano" with shell on "docker exec -it [CONTAINER ID] bash"
ENV TERM xterm
# Apache conf
# allow .htaccess with RewriteEngine
RUN a2enmod rewrite
# to see live logs we do : docker logs -f [CONTAINER ID]
# without the following line we get "AH00558: apache2: Could not reliably determine the server's fully qualified domain name"
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
# autorise .htaccess files
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
RUN chgrp -R www-data /var/www
RUN find /var/www -type d -exec chmod 775 {} +
RUN find /var/www -type f -exec chmod 664 {} +
EXPOSE 80
# start Apache2 on image start
CMD ["/usr/sbin/apache2ctl","-DFOREGROUND"]
The above code creates an Apache server and downloads all the necessities to run PHP on it.
Now let us update the docker-compose.yml
file to use the above image to create the backend.
backend:
container_name: simple-backend
build:
context: ./
dockerfile: Dockerfile
volumes:
- "./backend:/var/www/html/"
ports:
- 5000:80
This tells docker to create a container for the backend using the Dockerfile image and mount the local files in ./backend to the /var/www/html directory inside the container. We are also mapping port 5000 of your local machine to port 80 of the container.
Rerun docker compose up
to create the frontend and backend containers and access the backend application on 'http://localhost:5000' in your browser.
Make sure to update the JavaScript and PHP files so that we can access the backend API from the frontend
Database
Create a folder named db and write a dump.sql file that creates a table and dumps values into that table.
To make docker create our database let us append the following code into our docker-compose.yml
file.
database:
image: mysql:latest
environment:
MYSQL_DATABASE: web_commerce
MYSQL_USER: testuser
MYSQL_PASSWORD: password
MYSQL_ALLOW_EMPTY_PASSWORD: 1
volumes:
- "./db:/docker-entrypoint-initdb.d"
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- 8080:80
environment:
- PMA_HOST=database
- PMA_PORT=3306
depends_on:
- database
This creates a MySQL server and creates our table with the required values. To access our databases, we use a phpmyadmin image to create it and access our databases with the given username and password
Typing 'docker compose up' in your command line will create all the three frontend, backend and database containers. Use 'http://localhost:5000' to access the database.
Configuring backend container to access the database
Create a folder called app inside the backend folder.
To let the backend access the data from the database, we first create a config.php
file that gives the necessary data to access the database.
config.php
<?php
define("DB_HOST", "database");
define("DB_USERNAME", "testuser");
define("DB_PASSWORD", "password");
define("DB_NAME", "web_commerce");
We then create a database.php
file that contains the script that maintains our connection to the database.
database.php
<?php
class Database
{
protected $connection = null;
public function __construct()
{
try {
$this->connection = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
if (mysqli_connect_errno()) {
throw new Exception("Database connection failed!");
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
private function executeStatement($query = "", $params = [])
{
try {
$stmt = $this->connection->prepare($query);
if ($stmt === false) {
throw new Exception("Statement preparation failure: " . $query);
}
if ($params) {
$stmt->bind_param($params[0], $params[1]);
}
$stmt->execute();
return $stmt;
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
public function select($query = "", $params = [])
{
try {
$stmt = $this->executeStatement($query, $params);
$result = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
$stmt->close();
return $result;
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
return false;
}
}
We create a new web-commerce.php
file that contains the data that needs to be updated in the index.php
file.
<?php
require_once "./app/Database.php";
class Products extends Database
{
public function getProducts($limit)
{
return $this->select("SELECT * FROM products");
}
}
This was my index.php file after final updates. Use this as a reference to make your own.
<?php
header("Content-Type: application/json");
header('Access-Control-Allow-Origin: http://localhost:3000');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
session_start();
file_put_contents('php://stderr', print_r($_GET, TRUE)); // Log to the error log
require "./app/config.php";
require_once "./app/web_commerce.php";
$productModel = new Products();
$products = $productModel->getProducts(10);
$cart = [];
// Determine the API action based on the 'action' parameter in the query string
$action = isset($_GET['action']) ? $_GET['action'] : null;
switch ($action) {
case 'getProducts':
getProducts();
break;
case 'getProductDetails':
getProductDetails();
break;
case 'addToCart':
addToCart();
break;
case 'getCart':
getCart();
break;
default:
echo json_encode(["error" => "Invalid action"]);
break;
}
// Function to get all products
function getProducts() {
global $products;
echo json_encode($products);
}
// Function to get details of a single product by ID
function getProductDetails() {
global $products;
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
if ($id === null) {
echo json_encode(["error" => "Product ID is required"]);
return;
}
foreach ($products as $product) {
if ($product['id'] === $id) {
echo json_encode($product);
return;
}
}
echo json_encode(["error" => "Product not found"]);
}
// Function to add a product to the cart
function addToCart() {
global $cart, $products;
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
if ($id === null) {
echo json_encode(["error" => "Product ID is required"]);
return;
}
foreach ($products as $product) {
if ($product['id'] === $id) {
$cart[] = $product;
echo json_encode(["message" => "Product added to cart", "cart" => $cart]);
return;
}
}
echo json_encode(["error" => "Product not found"]);
}
// Function to get the current cart contents
function getCart() {
global $cart;
echo json_encode($cart);
}
?>
This will ensure that your backend can effortlessly fetch the values from your database.
Final folder structure:
C:.
│ docker-compose.yml
│ Dockerfile
│
├───backend
│ │ index.php
│ │
│ └───app
│ config.php
│ database.php
│ web_commerce.php
│
├───db
│ dump.sql
│
└───frontend
index.html
script.js
styles.css
Top comments (0)