Skip to content

HOWTO – Bootstrap Jenkins Controller on Control Node (ctrl-01)

Purpose:
Bring the control node (ctrl-01) up to a functioning Jenkins controller using Ansible roles, a Docker Engine baseline, and JCasC, in line with ADR-0608 and ADR-0603. At the end of this HOWTO, ctrl-01 runs a healthy Jenkins controller in Docker, ready to orchestrate platform pipelines.

Difficulty: Intermediate

Prerequisites


Demo

No academy walkthrough recording is linked here yet.

Context

This HOWTO applies when ctrl-01 already has:

  • A consistent OS baseline (as defined in the OS baseline rationale)
  • Network reachability and SSH access

The goal is to establish ctrl-01 as the CI control plane by:

  1. Applying the Docker Engine baseline (hybridops.common.docker_engine, ADR-0608) so Docker Engine and the docker compose plugin are available and managed.
  2. Deploying the Jenkins controller (hybridops.app.jenkins_controller, ADR-0603) as a containerised service with:
  3. Pinned image (jenkins/jenkins:ltshybridops/jenkins-controller:lts)
  4. Jenkins Configuration as Code (JCasC)
  5. Controlled ports and volumes under a dedicated jenkins_home_path

Standard:

  • Controller-only operation with numExecutors: 0

Bootstrap/lab:

  • Temporarily set numExecutors: 1 for smoke tests and initial validation before agents are configured.

Steps

1. Confirm baseline and connectivity

  1. Confirm Ansible connectivity:
ansible ctrl_nodes -m ping -i <your_inventory>
  1. Confirm OS facts:
ansible ctrl_nodes -m setup -a 'filter=ansible_distribution*' -i <your_inventory>
  1. Confirm collections are installed and visible:
ansible-galaxy collection list | rg '^hybridops\.'

Expected result

  • ansible ping succeeds
  • OS facts match the baseline
  • Required collections are present

2. Apply Docker Engine baseline

  1. Inventory group example:
[ctrl_nodes]
ctrl-01 ansible_host=ctrl01.example.internal
  1. Optional per-group variables:
docker_engine_state: present
docker_engine_enable: true
docker_engine_users:
  - hybridops
  1. Playbook:
- name: Bootstrap Docker Engine baseline on ctrl-01
  hosts: ctrl_nodes
  become: true
  collections:
    - hybridops.common
  roles:
    - role: hybridops.common.docker_engine
  1. Run:
ansible-playbook -i <your_inventory> deployment/playbooks/bootstrap_docker_ctrl01.yml

Expected result

  • docker version works
  • docker compose version works
  • docker service is enabled and running

3. Deploy the Jenkins controller

  1. Provide secrets and bootstrap executor count:
jenkins_admin_username: "admin"
jenkins_admin_password: "{{ vault_jenkins_admin_password }}"
jenkins_controller_num_executors: 1
jenkins_enable_healthcheck: true
  1. Playbook:
- name: Bootstrap Jenkins controller on ctrl-01
  hosts: ctrl_nodes
  become: true
  gather_facts: true
  collections:
    - hybridops.app
  roles:
    - role: hybridops.app.jenkins_controller
  1. Run:
ansible-playbook -i <your_inventory> deployment/playbooks/bootstrap_jenkins_ctrl01.yml

Expected result

  • Container jenkins-controller is running
  • HTTP endpoint is reachable on the configured port
  • JCasC is applied

4. Validate readiness

  1. Confirm container status:
docker ps | rg 'jenkins-controller'
  1. Confirm HTTP response:
curl -I http://ctrl-01:8080/ || curl -I http://localhost:8080/

A 302 Found redirect to /securityRealm/firstUser or /login is expected during initial bootstrap.

  1. Validate UI access:

  2. URL: http://ctrl-01:8080/

  3. Credentials: jenkins_admin_username / jenkins_admin_password

  4. Confirm executor state:

  5. Bootstrap/lab: 1 executor for validation

  6. Standard: reduce to 0 executors once agents are available

Validation

Complete when:

  • Docker baseline is applied and verified (docker and docker compose available).
  • Jenkins controller is running and healthy.
  • JCasC configuration is rendered and loaded.
  • Endpoint probes succeed and UI is reachable.
  • Evidence is captured for the run.

Recommended evidence capture:

  • docker compose ps output
  • docker logs jenkins-controller | tail -n 100
  • curl -I headers for the HTTP endpoint
  • Screenshot of the Jenkins UI after first-user / login redirect

Troubleshooting

Docker missing or Compose plugin not installed

  • Re-apply hybridops.common.docker_engine.
  • Confirm docker compose version on the host.
  • Confirm systemctl status docker.

Controller restart loop with JENKINS_HOME permission errors

  • Ensure host directory ownership matches the container UID/GID:
sudo chown -R 1000:1000 /opt/jenkins

Adjust to match the UID/GID configured in the role.

Endpoint never becomes healthy

  • Check logs:
docker logs jenkins-controller | tail -n 200
  • Validate the rendered compose YAML and JCasC YAML.
  • Increase controller memory if needed.

References


Maintainer: HybridOps
License: MIT-0 for code, CC-BY-4.0 for documentation