Skip to main content

GitLab CI Pipeline - Containerize and Deploy PHP Website

1244 words·
GitLab GitLab CI CI Pipeline PHP Alpine Debian Unprivileged Container
Table of Contents
GitHub Repository Available



Overview
#

In this tutorial I’m using the following setup based on Ubuntu 22.04 servers, GitLab is dockerized:

192.168.70.4 # GitLab
192.168.70.5 # GitLab Runner
192.168.70.6 # Deployment server with Docker installed



GitLab CI Pipeline
#

File and Folder Structure
#

GitLab-Repository
├── Dockerfile
├── .gitlab-ci.yml
├── php-example  # PHP example site
│   ├── css
│   │   └── styles.css
│   └── index.php
└── README.md

CI Pipeline Manifest
#

  • .gitlab-ci.yml
### Variables
variables:
  DEPLOY_IP: "192.168.70.6"  # Deployment server IP
  DEPLOY_USER: "gitlab-deployment"  # Deployment server SSH user
  DEPLOY_PORT_HOST: 8080  # Host port
  DEPLOY_PORT_CONT: 8000  # Container port
  CONTAINER_NAME: "php-example"  # Name of the deployed container

  # Define the image name, tagging it with the GitLab CI registry and the current commit SHA
  IMAGE_SHA: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA


### Stages
stages:
  - build
  - deploy


### Build Container Image
build_image:
  image: docker:stable
  stage: build
  services:
    - docker:dind
  variables:
    DOCKER_TLS_CERTDIR: ""
  before_script:
    # Login to GitLab Container Registry using predefined CI/CD variables
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    # Build the Docker image from the specified Dockerfile in the Dockerfiles directory
    - docker build --pull -t $IMAGE_SHA -f Dockerfile .
    # Push the built Docker image to the GitLab Container Registry
    - docker push $IMAGE_SHA
  rules: 
    # Rule: Run this job only for the main branch and if the specified Dockerfile exists
    - if: $CI_COMMIT_BRANCH == "main"
      exists:
        - Dockerfile


### Deploy Container to Virtual Machine
deploy_container:
  stage: deploy
  image: alpine:latest
  needs:
    - build_image  # Run this job only if the 'build_image' job succeeds
  before_script:
    # Update the package index, install the OpenSSH client for SSH connections
    - apk update && apk add openssh-client
    # If the private SSH key file ($ID_RSA) exists, set secure permissions (read/write for the owner only)
    - if [ -f "$ID_RSA" ]; then chmod og= $ID_RSA; fi
  script:
    #  SSH into the deployment server, log in to the GitLab Container registry
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_IP "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
     # SSH into the deployment server, pull the image from the registry
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_IP "docker pull $IMAGE_SHA"
    # SSH into the deployment server, remove the existing container (if it exists)
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_IP "docker container rm -f $CONTAINER_NAME || true"
    # SSH into the deployment server, run the new container
    - ssh -i $ID_RSA -o StrictHostKeyChecking=no $DEPLOY_USER@$DEPLOY_IP "docker run -d -p $DEPLOY_PORT_HOST:$DEPLOY_PORT_CONT --restart=unless-stopped --name $CONTAINER_NAME $IMAGE_SHA"
  rules:
    # Rule: Run this job only for main branch
    - if: $CI_COMMIT_BRANCH == "main"

Dockerfile
#

Alpine Version
#

  • Dockerfile
# Start with the lightweight Alpine Linux image with PHP
FROM php:8.2-alpine

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

# Create a non-root user and group
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Copy the PHP application from the GitLab repository to the container
COPY php-example /var/www/html

# Change ownership of the application files to the non-root user
RUN chown -R appuser:appgroup /var/www/html

# Expose the default PHP development server port
EXPOSE 8000

# Switch to the non-root user
USER appuser

# Start PHP's built-in development server
ENTRYPOINT ["php", "-S", "0.0.0.0:8000"]

Debian Version
#

  • Dockerfile
# Debian 12 based Apache base image
FROM php:8.1.31-apache-bookworm

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

# Create a non-root user and group
RUN useradd --system --no-create-home appuser

# Copy the PHP application from the GitLab repository to the container
COPY php-example /var/www/html

# Change ownership of the application files to the non-root user
RUN chown -R appuser:appuser /var/www/html

# Expose the default PHP development server port
EXPOSE 8000

# Switch to the non-root user
USER appuser

# Start PHP's built-in development server
ENTRYPOINT ["php", "-S", "0.0.0.0:8000"]

PHP Example Site
#

PHP File: index.php
#

  • php-example/index.php
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jklug.work</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css">
</head>
<body>
    <h1>PHP Example:</h1>

    <form method="POST" action="">
        <label for="name">Enter your name:</label>
        <input type="text" id="name" name="name" required>
        <button type="submit">Submit</button>
    </form>

    <?php
    // Check if the form has been submitted
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // Get the submitted name from the form
        $name = htmlspecialchars($_POST['name']); // Prevent XSS
        echo "<h2>Hello, $name!</h2>";
    }
    ?>
</body>
</html>

CSS: styles.css
#

  • php-example/css/styles.css
/* General styling for the body */
body {
    font-family: Arial, sans-serif;
    background-color: #f0f0f0;
    margin: 0;
    padding: 0;

    /* Flexbox centering */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh; /* Full viewport height */
}


/* Header styling */
h1 {
    color: #333;
    margin: 0 0 10px 0; /* Remove default margin and add spacing */
}
h2 {
    color: #333;
}

/* Paragraph styling */
p {
    color: #666;
    font-size: 18px;
    margin: 10px 0; /* Add some spacing */
}


/* Button styling */
button {
    background-color: #d2691e;
    color: white;
    border: none;
    padding: 10px 20px;
    font-size: 16px;
    border-radius: 5px;
    cursor: pointer;
    margin-top: 20px; /* Add space above the button */
}

/* Button hover effect */
button:hover {
    background-color: #8b4513;
}



Verify Deployment
#

Alpine Version
#

Verify the container:

# List Docker containers
docker ps

# Shell output:
CONTAINER ID   IMAGE                                                                                      COMMAND                  CREATED         STATUS         PORTS                                         NAMES
43d82a348954   gitlab-registry.jklug.work/php/php-example/main:38dbfad2323f6f5b04e7902272a80f3519ed78dd   "docker-php-entrypoi…"   3 seconds ago   Up 3 seconds   0.0.0.0:8080->8000/tcp, [::]:8080->8000/tcp   php-example

Verify the container user:

# Access the container terminal
docker exec -it 43d82a348954 sh

# List details of current user
id

# Shell output:
uid=100(appuser) gid=101(appgroup) groups=101(appgroup)

Verify the container image:

# List docker images
docker images

# Shell output:
REPOSITORY                                                     TAG                                        IMAGE ID       CREATED         SIZE
gitlab-registry.jklug.work/php/php-example/main                38dbfad2323f6f5b04e7902272a80f3519ed78dd   0363ec56ccfd   2 minutes ago   104MB

Verify the Python Flask application:

# Curl the container
curl localhost:8080

# Shell output:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jklug.work</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css">
</head>

Debian Version
#

Verify the container:

# List Docker containers
docker ps

# Shell output:
CONTAINER ID   IMAGE                                                                                      COMMAND                 CREATED         STATUS         PORTS                                                 NAMES
5024a38f1d2e   gitlab-registry.jklug.work/php/php-example/main:06cc4b04291ea8a0b5c0f70184b68226fe8e3e0c   "php -S 0.0.0.0:8000"   8 seconds ago   Up 7 seconds   80/tcp, 0.0.0.0:8080->8000/tcp, [::]:8080->8000/tcp   php-example

Verify the container user:

# Access the container terminal
docker exec -it 5024a38f1d2e sh

# List details of current user
id

# Shell output:
uid=999(appuser) gid=999(appuser) groups=999(appuser)

Verify the container image:

# List docker images
docker images

# Shell output:
REPOSITORY                                                     TAG                                        IMAGE ID       CREATED          SIZE
gitlab-registry.jklug.work/php/php-example/main                06cc4b04291ea8a0b5c0f70184b68226fe8e3e0c   7cf8609306f5   41 seconds ago   500MB

Verify the Python Flask application:

# Curl the container
curl localhost:8080

# Shell output:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jklug.work</title>
    <link rel="stylesheet" type="text/css" href="css/styles.css">
</head>



Local Test
#

To run the PHP site locally on a Linux server, install PHP and a webserver like Apache2 and place the PHP site into the Apache web root /var/www/html/.

The file and folder structure should look like this:

/var/www/
        └── html
            ├── css
            │   └── styles.css
            └── index.php

Install PHP & Apache
#

# Install PHP and dependencies (Debian based distributions)
sudo apt install php libapache2-mod-php php-mysql -y
# Verify PHP installation / check version
php -v

# Shell output:
PHP 8.3.6 (cli) (built: Sep 30 2024 15:17:17) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies

Install Apache to enable local PHP testing:

# Instapp Apache
sudo apt install apache2 -y

# Remove the default html file
sudo rm /var/www/html/index.html

Verify PHP Project
#

To test the PHP website, open the server IP in a browser, for example 192.168.30.16:80



Links #

jueklu/gitlab-ci-deploy-php-site

GitLab CI pipeline that containerizes and deploys a simple PHP site via Alpine container.

CSS
0
0