Contents

Auto-Deploy Jenkins to a remote server (with free SSL) via Ansible (HowTo)

Jenkins is one of the most popular CI/CD (Continuous Integration and Continuos Deployment) tools out there. It's greatly loved in the Tech community because it's free, open-source and self-hosted. There're many reasons why you might want to deploy a Jenkins server, to build personal projects, practice using the software, for company production use, etc.

Whatever your reason, one thing is sure, deploying your server with Ansible will make the process automatic, repeatable, re-useable, versionable and shareable. Today we'll walkthrough how to create an Ansible playbook that deploys Jenkins to a remote server, with free Letsencrypt SSL certificate (that auto-renews).

Info
I’ve uploaded the entire codebase for this project here on Github.

Let’s jump in

1. Acquire a Remote Server

The first step to deploying Jenkins on a server is to acquire the server itself. A remote server can be provisioned from one of many different places. Cloud providers such as DigitalOcean, Google Cloud, AWS, etc, offer some of the most cost-effective VPS (virtual private server) plans on the market.

Depending on your needs you can provision a server either manually or via IaC (Infrastructure as Code) from any one of these providers.

Tip
For the purpose of this tutorial, be sure to provision your server instance with access to the public internet (and hence a public Ipv4 address).
Info

It’s considered a best practice to run ansible commands on a node via a dedicated user. To help expedite the process of creating a dedicated user with the appropriate permissions for Ansible, use the following userdata startup script to lauch your VPS.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#cloud-config
ssh_pwauth: false

users:
  - name: ansible
    gecos: Ansible User
    groups: users,admin,wheel
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    lock_passwd: true
    ssh_authorized_keys:
      - "ssh-public-key"

Replace "ssh-public-key" with the appropriate SSH public key for connection to your server.

2. Setup for Ansible

Before we dive in to creating our Playbook, we need to setup a few things to make sure our workflow succeds.

2.1 Verify SSH connectivity to remote server

Ansible connects to node servers via SSH. Since we’re dealing with a single server for this tutorial we’ll need to connect to it initialy, manually, to verify connectivity.

Tip

Part of the process of deploying a VPS is setting up SSH access to it. Wherever you deploy your VPS, you should have access to the private key file for SSH connection to your server.
Initiate an SSH connection to your server using

1
ssh -i location/to/keyfile username@server_ip 
Info
Be sure to store your SSH private key securely on your Ansible host, and keep track of it’s location because it’ll be used at a next step to grant Ansible access to your VPS.

2.2 Setup DNS records for our Jenkins domain

In order to ensure access to your Jenkins server from the appropriate domain, you need to point your domain name to the IP address of your Server. This will be achieved by creating a DNS A record. This can be done a variety of different ways (via a variety of interfaces) depending of the Domain Name Service in charge of your domain. The specifics are out of the scope of this tutorial.

Warning
Without the proper DNS record set, your Playbook will fail to issue SSL to your Jenkins server because Certbot (the client used to issue the certificates) will be unable to verify that you control the domain for which the SSL cert will be issued. This will cause your deployment to fail, so ensure you setup the appropriate DNS record.

2.3 Create an Ansible Config file

To work correctly, Ansible needs a few important things configured. We do these in the ansible.cfg file. Ansible looks at this file automatically before execution, to find the important things it needs to function. Our ansible.cfg file will have the following contents.

1
2
3
4
[defaults]
inventory = inventory.txt
private_key_file = key.txt
remote_user = ansible

This file configures the following important things:

  • It tells Ansible that our inventory file, the file that lists the addresses of all the servers we’d like ansible to manage, is a file name inventory.txt and found in the current directory.
  • It tells ansible that the private key file for SSH connectivity to the host is a file named key.txt, located too in the current directory
  • It configures the username ansible for SSH connection to the server. This will be the case if you configured your server with the cloud-init scrip provided previously, if not, feel free to change this value to whatever your username happens to be.
Info
In keeping with this configuration you should store the SSH private key file for connection to the server in a file called key.txt in the current directory, so Ansible can find it.
Warning
Remember to NOT check-in your key.txt file to version control. Especially to a public repository on Github.

Once you’ve verified SSH connectivity to your server, setup the appropriate DNS record & created a config file, you’re ready to create your Playbook.

3. Create the Playbook

It’s time to create your Ansible Playbook. To keep our solution simple, and modular, we’re going to make use of Ansible roles. Here’s a summary of the steps that lead to our desired outcome:

  • Setup an Nginx proxy for Jenkins
  • Acquire an SSL certificate for our server
  • Install Jenkins


Now in code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
- hosts: all
  become: true
  roles:
    - nginx-proxy

- hosts: all
  become: true
  roles:
    - certbot

- hosts: all
  become: true
  roles:
    - jenkins

4. Create accompanying Roles

Next you need to create the roles for the different aspects of the workflow. The directory structure for your roles should look like so:

/jenkins-via-ansible/tree.png

4.1 Inside the nginx-proxy role

Create a tasks/mail.yml file

With the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- name: Install NginX
  tags: nginx,install
  ansible.builtin.apt:
    name: nginx
    state: latest
    update_cache: true

- name: Enable NginX
  ansible.builtin.service:
    name: nginx
    enabled: true

- name: Config Jenkins Proxy
  ansible.builtin.template:
    src: "jenkins-proxy.j2"
    dest: /etc/nginx/sites-available/jenkins-proxy.conf
    owner: root
    group: root
    mode: 0644
  notify: restart_nginx

- name: Enable Proxy
  ansible.builtin.file:
    src: /etc/nginx/sites-available/jenkins-proxy.conf 
    dest: /etc/nginx/sites-enabled/jenkins-proxy.conf
    state: link
  notify: restart_nginx

Create a handlers/mail.yml file

With the following content:

1
2
3
4
- name: restart_nginx
  ansible.builtin.service:
    name: nginx
    state: restarted

Create a templates/jenkins-proxy.j2 file

With the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
server { 

    listen 80; # Port for incoming traffic (e.g., 80 for HTTP, 443 for HTTPS)
    server_name {{ server_name }}; # Your domain name or IP address

    # Location block for handling proxied requests

    location / {
        proxy_pass {{ jenkins_localhost }}; # Upstream server address (e.g., http://127.0.0.1:8080)
        
        # Optional settings:
        
        proxy_set_header Host $host; # Preserve host header for upstream server
        proxy_set_header X-Real-IP $remote_addr; # Forward client IP address
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Forward proxy chain

        # Additional settings as needed (e.g., caching, rewrites)
    }

}

4.2 Inside the certbot role

Create a tasks/mail.yml file

With the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
- name: Install Snapd
  ansible.builtin.apt:
    name: snapd
    state: latest

- name: Install Certbot
  community.general.snap:
    name: certbot
    classic: true
    state: present

- name: Create symlink    
  ansible.builtin.file:
    src: /snap/bin/certbot 
    dest: /usr/bin/certbot
    state: link

- name: Run certbot
  ansible.builtin.shell: 
    cmd: certbot --nginx -n --agree-tos --email {{ server_email }} --domains {{ server_name }}

4.3 Inside the jenkins role

Create a tasks/mail.yml file

With the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- name: Install JDK
  ansible.builtin.apt:
    pkg:
      - fontconfig
      - openjdk-17-jre

- name: Get Jenkins Repo Key
  ansible.builtin.get_url:
    url: https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
    dest: /usr/share/keyrings/jenkins-keyring.asc

- name: Set Jenkins Source
  ansible.builtin.lineinfile:
    path: /etc/apt/sources.list.d/jenkins.list
    line: deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/
    create: yes

- name: Install Jenkins
  ansible.builtin.apt:
    name: jenkins
    state: present
    update_cache: true

- name: Enable Jenkins
  ansible.builtin.service:
    name: jenkins
    enabled: true

5. Configure Jenkins debug outputs

The very first time you setup your Jenkins server, to get started using the Web UI, you have to retrieve a secret key from a file in the Jenkins server, to prove you control the server. We can retrieve this key and make Ansible output it to us by adding the following lines to our role/jenkins/tasks/main.yml file:

1
2
3
4
5
6
7
8
- name: Check Jenkins Status
  ansible.builtin.shell:
    cmd: systemctl status jenkins
  register: check_jens

- name: Debug
  debug:
    msg: "{{ check_jens.stdout_lines }}"
Info

This addition prints out the value of

1
systemctl status jenkins

6. Deploy your playbook

This is where you deploy your Playbook, and have Jenkins auto setup for you. To deploy your Playbook you must provide appropriate values to the 3 variables defined accross our roles.

  • The {{jenkins_localhost}} variable points to the localhost address of Jenkins. This is typically http://localhost:8080
  • The {{server_name}} variable provides the domain name on which the Jenkins server is to be accessible. This is the domain for which you set DNS records previously.
  • The {{server_email}} variable provides the email address of the server admin (this is required).
Info

You can choose to provide the value to these variables a number of different ways. You could provide them at runtime, in which case you would deploy your Playbook with the below command:

1
2
ansible-playbook --extra-vars "server_name=domain-name jenkins_localhost=http://127.0.0.1:8080 
server_email=email" playbook.yml 

Replacing domain-name & email with appropriate values.

Here’s an example:

1
2
3
ansible-playbook --extra-vars "server_name=jen.obimadu.pro 
jenkins_localhost=http://127.0.0.1:8080 
server_email=hello@obimadu.pro" playbook.yml

For additional ways to provide the value of the variables visit Using Variables

AND THAT’S IT 🥳 🎉 You now have a fully automated way of deploying (and/or re-deploying) your Jenkins Server!