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¶
ctrl-01provisioned as a VM according to:- ADR-0012 – Control Node Runs as a VM (Cloud-Init); LXC Reserved for Light Helpers
- ADR-0017 – Operating System Baseline and the OS baseline whitepaper
- Ansible control machine with:
- Access to the HybridOps repositories
- SSH connectivity to
ctrl-01(for example via inventory groupctrl_nodes) - Collections and roles:
hybridops.common.docker_engine(ADR-0608)hybridops.app.jenkins_controller(ADR-0603)- Secrets strategy in place:
- A way to provide
jenkins_admin_password(for example AKV, Ansible Vault, or environment variables)
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:
- Applying the Docker Engine baseline (
hybridops.common.docker_engine, ADR-0608) so Docker Engine and thedocker composeplugin are available and managed. - Deploying the Jenkins controller (
hybridops.app.jenkins_controller, ADR-0603) as a containerised service with: - Pinned image (
jenkins/jenkins:lts→hybridops/jenkins-controller:lts) - Jenkins Configuration as Code (JCasC)
- Controlled ports and volumes under a dedicated
jenkins_home_path
Standard:
- Controller-only operation with
numExecutors: 0
Bootstrap/lab:
- Temporarily set
numExecutors: 1for smoke tests and initial validation before agents are configured.
Steps¶
1. Confirm baseline and connectivity¶
- Confirm Ansible connectivity:
ansible ctrl_nodes -m ping -i <your_inventory>
- Confirm OS facts:
ansible ctrl_nodes -m setup -a 'filter=ansible_distribution*' -i <your_inventory>
- Confirm collections are installed and visible:
ansible-galaxy collection list | rg '^hybridops\.'
Expected result
ansible pingsucceeds- OS facts match the baseline
- Required collections are present
2. Apply Docker Engine baseline¶
- Inventory group example:
[ctrl_nodes]
ctrl-01 ansible_host=ctrl01.example.internal
- Optional per-group variables:
docker_engine_state: present
docker_engine_enable: true
docker_engine_users:
- hybridops
- Playbook:
- name: Bootstrap Docker Engine baseline on ctrl-01
hosts: ctrl_nodes
become: true
collections:
- hybridops.common
roles:
- role: hybridops.common.docker_engine
- Run:
ansible-playbook -i <your_inventory> deployment/playbooks/bootstrap_docker_ctrl01.yml
Expected result
docker versionworksdocker compose versionworksdockerservice is enabled and running
3. Deploy the Jenkins controller¶
- 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
- 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
- Run:
ansible-playbook -i <your_inventory> deployment/playbooks/bootstrap_jenkins_ctrl01.yml
Expected result
- Container
jenkins-controlleris running - HTTP endpoint is reachable on the configured port
- JCasC is applied
4. Validate readiness¶
- Confirm container status:
docker ps | rg 'jenkins-controller'
- 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.
-
Validate UI access:
-
URL:
http://ctrl-01:8080/ -
Credentials:
jenkins_admin_username/jenkins_admin_password -
Confirm executor state:
-
Bootstrap/lab: 1 executor for validation
- Standard: reduce to 0 executors once agents are available
Validation¶
Complete when:
- Docker baseline is applied and verified (
dockeranddocker composeavailable). - 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 psoutputdocker logs jenkins-controller | tail -n 100curl -Iheaders 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 versionon 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¶
- ADR-0012 – Control Node Runs as a VM (Cloud-Init); LXC Reserved for Light Helpers
- ADR-0017 – Operating System Baseline
- ADR-0202 – Adopt RKE2 as Primary Runtime for Platform and Applications
- ADR-0608 – Docker Engine baseline
- ADR-0603 – Run Jenkins Controller on Control Node, Agents on RKE2
- Runbook – Bootstrap Jenkins Controller on Control Node
- Evidence Map
Maintainer: HybridOps
License: MIT-0 for code, CC-BY-4.0 for documentation