Files
web-log/container/site/content/posts/jenkins-deploy-website/index.md
T
nicholas 2e9bd0e409
Build & Push Hugo Site Image / Build & Push Image (push) Successful in 9s
Build & Push Hugo Site Image / deploy (push) Successful in 16s
add tags, descriptions to posts (#38)
2026-08-05 16:24:33 -05:00

8.2 KiB

+++ categories = ["software"] tags = ["automation","docker","jenkins","nginx"] date = 2025-03-30T18:00:00-06:00 description = "Creating a Jenkins pipeline that builds and deploys a personal website into an Nginx container from a self-hosted Git repository." draft = false slug = "jenkins-deploy-website" title = "🤵🏻 Automating Personal Website Deployment" author = "nicholas" +++

I have a simple personal website that currently serves as a landing page containing links that direct visitors to my other websites. I want to use Jenkins to automate the deployment of this site. In my [last post on this topic]({{< relref "posts/jenkins" >}}), I set up a Jenkins web UI and build agent in container using Docker. I will use this container to automate the process of deploying my website.

The plan

I will configure Jenkins to monitor the main branch of my website repository and copy the repo to a host directory that is bind-mounted to both my Jenkins and Nginx containers. This host directory will be bind-mounted to Nginx's document root so the website is automatically published to the web server.

Jenkins

I am using a pipeline definition based on a pipeline script from SCM. Instead of storing the pipeline script in Jenkins directly, I will use the script defined in the repository. This has the benefit of being much easier to maintain, as I only need to make changes to the repository. It fully automates the process by eliminating the step where I must configure the pipeline script before I push changes. Below is the Jenkinsfile I used to test my setup.

Jenkins pipeline configuration

  • First, I need to add a new pipeline item to Jenkins.

{{< image src="images/jenkins-new-pipeline.jpg" caption="New pipeline 'personal-website-pipeline'" >}}

  • Next I configure the pipeline with the following settings: {{< image src="images/jenkins-pipeline-configuration.jpg" caption="person-website-pipeline settings" >}}

I have formatted the relevant settings in YAML below:

Definition: Pipeline script from SCM
SCM: Git
Repositories:
  - Repository URL: https://git.uuard.com/Ward/personal-website.git
  - Credentials: gitea
Branches to build:
  - Branch Specifier: */main
Script Path: Jenkinsfile

Jenkins pipeline configuration details

  • Pipeline Definition

    • The pipeline is defined as a Pipeline script from SCM, meaning the Jenkinsfile is stored in the repository rather than inside Jenkins itself.
  • Source Code Management (SCM) Settings

    • SCM: Git → Jenkins fetches the pipeline from a Git repository.
    • Repository URL: https://git.uuard.com/Ward/personal-website.git → This is the Git repository where the Jenkinsfile and source code are stored.
    • Credentials: gitea → Authentication is handled using stored credentials for Gitea.
  • Branch to Build

    • Branch Specifier: */main → Jenkins monitors and builds from the main branch.
  • Script Path

    • Jenkinsfile → Jenkins looks for the pipeline script inside the repository at this path.

Jenkinsfile pipeline definition

pipeline {
    agent any
    environment {
        DEPLOY_DIR = '/var/jenkins_home/deployments/personal-website'
    }
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        stage('copy to nginx server') {
            steps {
                sh """
                    rm -rf ${DEPLOY_DIR}/*
                    cp -r * ${DEPLOY_DIR}/
                    chmod -R 755 ${DEPLOY_DIR}
                """
            }
        }
    }
    post {
        failure {
            echo "Build Failed"
        }
    }
}

Jenkinsfile pipeline definition details

  • Pipeline Declaration

    • Defines a declarative pipeline with pipeline {} block.
  • Agent

    • agent any → Runs on any available Jenkins agent. I am currently building on the built-in node (agent).
  • Environment Variables

    • DEPLOY_DIR = '/var/jenkins_home/deployments/personal-website': Sets the deployment directory path. This path is inside the Docker volume jenkins-data. The var/jenkins_home/deployments/personal-website directory has been bind-mounted to a directory on the host machine, which is also shared as a bind-mount to the document root of Nginx web server running in a separate Docker container.
  • Stage: Checkout

    • checkout scm Retrieves the source code from gitea (scm is configured in the Jenkins pipeline)
  • Stage: Copy to Nginx Server

    • Runs shell commands in sh """ ... """ block:
      • rm -rf ${DEPLOY_DIR}/* → Deletes existing files in the deployment directory.
      • cp -r * ${DEPLOY_DIR}/ → Copies all files from the workspace to the deployment directory.
      • chmod -R 755 ${DEPLOY_DIR} → Sets permissions for readability and execution.
  • Post Actions

    • post { failure { echo "Build Failed" } } → Displays "Build Failed" if any stage fails.

Jenkins Container Configuration

I need to modify the docker-compose.yml file to reflect my description above of how the whole system should work. I add a bind-mount to the host directory nginx-personal-website/html (which my webserver will also have bind-mounted) to the container directory at var/jenkins_home/deployments/personal-website. In the pipeline definition above I set the DEPLOY_DIR to map to this container directory.

services:
  jenkins:
    image: jenkins/jenkins:jdk17
    container_name: jenkins
    hostname: jenkins
    restart: always
    volumes:
      - jenkins-data:/var/jenkins_home/
      - ../nginx-personal-website/html:/var/jenkins_home/deployments/personal-website
    ports:
      - "2376:2376"
      - "8882:8080"
      - "50000:50000"

volumes:
  jenkins-data:
    external: true

Nginx

I will use an Nginx container for my web server. The container will simply serve whatever content is in the document root, in this case /usr/share/nginx/html. This is the behavior of Nginx, and I can change this by supplying a different configuration. For now, the default configuration is enough. I can add custom configuration later.

Nginx Container

services:
  nginx:
    image: nginx:alpine
    container_name: nginx-personal-website
    restart: unless-stopped
    ports:
      - "8800:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
      #- ./conf:/etc/nginx:ro

Testing

As soon as I push a change to the main branch of my repo, Jenkins should execute the pipeline script defined in the Jenkinsfile. {{< image src="images/jenkins-pipeline-run-overview.jpg" caption="Jenkins test build - success" >}}

Improvements - Git Hook

Polling is bad and Jenkins agrees. In the tooltip for selecting Poll SCM build trigger:

Note that this is going to be an expensive operation for CVS, as every polling requires Jenkins to scan the entire workspace and verify it with the server. Consider setting up a
"push" trigger to avoid this overhead, as described in this document

So instead I select Trigger builds remotely (e.g., from scripts)

{{< image src="images/jenkins-trigger-build-remotely.jpg" caption="trigger builds remotely" >}}

To accomplish triggering builds remotely, I will implement a hook that executes a script after I push to the repository. The script will send a post request to Jenkins which will trigger the build.

This script goes in the gitea/data/git/repositories/<user>/<repository.git>/hooks/post-receive.d directory. The way this works is Gitea first automatically runs a built-in post-receive hook, which is a script that iterates through all of the files inside the post-receive.d directory and executes them as well.

Here is the script I use to trigger a build:

#!/bin/bash

JENKINS_URL="https://build.uuard.com"
JENKINS_JOB="/job/personal-website-pipeline/build"
JENKINS_USER="nicholas"
JENKINS_API_TOKEN="<api-token>"
JENKINS_BUILD_URL="$JENKINS_URL$JENKINS_JOB"

curl -X POST -u "$JENKINS_USER:$JENKINS_API_TOKEN" "$JENKINS_BUILD_URL"

exit 0

When Jenkins receives the HTTP POST request at https://build.uuard.com/job/personal-website-pipeline/build with valid authentication it will initiate the build.

Done.