Clean history, git LFS

This commit is contained in:
2025-07-05 15:36:34 +00:00
commit 70423ce723
141 changed files with 8484 additions and 0 deletions
@@ -0,0 +1,191 @@
+++
categories = ["software"]
tags = ["automation","docker","jenkins","nginx"]
date = 2025-03-30T18:00:00-06:00
description = ""
draft = false
slug = "jenkins-deploy-website"
title = "🤵🏻 Automating Personal Website Deployment"
author = "nicholas"
+++
I have a simple [personal website](https://nicholas.uuard.com/) 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](https://git.uuard.com/Ward/personal-website) 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:
```yaml
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
```Jenkinsfile
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.
```yaml
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
```yaml
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:
```bash
#!/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.