Clean history, git LFS
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user