191 lines
8.1 KiB
Markdown
191 lines
8.1 KiB
Markdown
+++
|
|
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. |