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 themainbranch.
- Branch Specifier:
-
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.
- Defines a declarative pipeline with
-
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 volumejenkins-data. Thevar/jenkins_home/deployments/personal-websitedirectory 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 scmRetrieves 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.
- Runs shell commands in
-
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.