In Part 1 — Jenkins Fundamentals: What Are We Actually Operating?, we built the mental model for Jenkins: the Controller receives a build request, the Queue holds work waiting for execution capacity, an Executor provides a slot to run the workload, and a Workspace becomes the build's working directory.
Now we are going to make that model concrete.
In Part 2, we will launch a single Amazon EC2 instance, install a Jenkins Controller manually, complete the initial setup, and run our first Pipeline directly on the Controller. This is intentionally the simplest architecture in the series: easy to understand, easy to inspect, and deliberately not our production target.
By the end of the lab, the concepts introduced in Part 1 should no longer feel abstract. You will be able to identify the Jenkins process, JENKINS_HOME, the built-in node, its Executor, a real Workspace, Console Output, and an archived Artifact.
1. What We Are Building
For our first real Jenkins environment, we will use a single EC2 instance:
The Jenkins Controller and the first build workload will share the same machine.
That is an important detail. It makes the lab simple because there is only one host to understand, but it also means the control plane and build workload compete for the same CPU, memory, disk, and failure domain.
We will keep that limitation on purpose. In Part 3 — Adding Static Jenkins Agents on AWS EC2, we will move build execution onto a separate EC2 Agent and compare the two models directly.
Lab choices
For this article, we will use:
- Operating system: Ubuntu Server 24.04 LTS
- Instance type:
t3.medium(2 vCPU, 4 GiB memory) - Storage: 20 GiB gp3 EBS root volume
- Jenkins: Jenkins LTS
- Java: OpenJDK 21
- Access: SSH for administration and TCP/8080 from your own public IP for the Jenkins UI
A smaller instance may work for a very small lab, but Jenkins plugin installation and builds can consume noticeable memory. Using 4 GiB gives us enough room to observe Jenkins without immediately turning memory pressure into the main lesson.
2. Provision the EC2 Baseline
We will assume you are already comfortable launching and connecting to EC2, so we will keep the infrastructure setup intentionally brief.
Create an EC2 instance named jenkins-controller-lab using Ubuntu Server 24.04 LTS, a t3.medium instance, and a 20 GiB gp3 root volume. Place it in a subnet with outbound Internet access so the host can download Ubuntu packages and Jenkins plugins.
For this learning environment, allow SSH/22 and TCP/8080 only from your own public IP. Jenkins itself does not need AWS API permissions for this article, so avoid attaching broad IAM policies such as AdministratorAccess. If you prefer Session Manager instead of SSH, attach only the instance profile permissions required for Systems Manager.
At this stage, both the operating system and JENKINS_HOME will live on the root EBS volume. We will intentionally keep the design simple now and separate Jenkins state from compute later in the series.
Once the instance is healthy, connect to it using SSH or Session Manager and continue with the Jenkins installation.
3. Install Java 21
Jenkins runs on the JVM, so Java is the first runtime dependency we need.
Update the package index and install OpenJDK 21:
sudo apt update sudo apt install -y fontconfig openjdk-21-jre Verify the runtime:
java -version You should see Java 21 in the output.
Why install Java before Jenkins? Because the Jenkins service needs a supported Java runtime when it starts. Installing the runtime first also makes failures easier to reason about: we can validate Java independently before adding Jenkins.
ℹ️
Version note: this lab uses Java 21 with Jenkins LTS. Jenkins also supports newer Java releases for supported Jenkins versions, but Java 21 keeps the lab simple and broadly compatible. If you are reading this article much later, check the Jenkins Java Support Policy before installing.
4. Install Jenkins LTS
For the series, we will use the Jenkins Long-Term Support (LTS) release rather than the weekly release.
Add the Jenkins stable package signing key and repository:
sudo wget -O /etc/apt/keyrings/jenkins-keyring.asc \ https://pkg.jenkins.io/debian-stable/jenkins.io-2026.key echo "deb [signed-by=/etc/apt/keyrings/jenkins-keyring.asc]" \ https://pkg.jenkins.io/debian-stable binary/ | sudo tee \ /etc/apt/sources.list.d/jenkins.list > /dev/null Update the package index and install Jenkins:
sudo apt update sudo apt install -y jenkins The package creates a jenkins system user and installs Jenkins as a systemd service.
Enable Jenkins at boot and start it:
sudo systemctl enable jenkins sudo systemctl start jenkins Then verify the service:
sudo systemctl status jenkins --no-pager You are looking for:
Active: active (running) You can also confirm that Jenkins is listening on port 8080:
sudo ss -lntp | grep 8080 And inspect recent service logs:
sudo journalctl -u jenkins --no-pager -n 50 At this point, we can connect the installation back to Part 1: the Jenkins Controller is now a real Java process running as a Linux service on our EC2 instance.
5. Unlock Jenkins and Complete the Initial Setup
Open the following address in your browser:
http://<EC2_PUBLIC_IP>:8080 If the Security Group is correct and Jenkins is running, you should see the Unlock Jenkins page.
Jenkins creates a one-time administrator password during the initial startup. Retrieve it from the server:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword Paste the value into the browser.
Plugin installation
For this learning environment, choose Install suggested plugins.
The purpose of Part 2 is to understand the Jenkins runtime and execution model, not to design plugin governance yet. Suggested plugins give us the common Pipeline capabilities needed for the lab without turning the article into a plugin-selection exercise.
In production, plugin installation should be much more deliberate. Plugin versions, dependencies, upgrades, rollback, and Configuration as Code will appear later in the series.
Create the first administrator
Create the first Jenkins administrator account and finish the setup wizard.
Once the Jenkins dashboard appears, stop for a moment. We now have a usable Controller, but before creating a job, let us inspect what the installer actually created.
6. Find JENKINS_HOME on the Server
In Part 1, JENKINS_HOME was an architectural concept. Now we can see it.
For this package-based Ubuntu installation, the default location is:
/var/lib/jenkins Inspect it:
sudo ls -lah /var/lib/jenkins You will see directories and files related to Jenkins configuration and runtime state. The exact contents evolve as plugins are installed and jobs are created, but this directory is the persistent state boundary introduced in Part 1.
Check its current size:
sudo du -sh /var/lib/jenkins Also inspect the service definition:
systemctl cat jenkins And confirm which user owns the Jenkins process:
ps -ef | grep '[j]enkins' If the Jenkins process restarts, or the EC2 instance reboots while the EBS volume remains intact, JENKINS_HOME remains available. If the storage containing this directory is lost without a backup, the Jenkins state on it is lost as well.
That is why storage and recovery become first-class design decisions once we move beyond this lab.
7. Understand the Built-In Node
Go to: Manage Jenkins -> Nodes. You should see the Jenkins built-in node.
For this first lab, the Controller is also providing execution capacity. In other words:
This is the simplest possible Jenkins architecture.
It is also the exact situation we warned about in Part 1: control-plane work and build work share the same compute resources.
We are going to use it once because it makes the execution model easy to observe. In Part 3, we will deliberately move builds away from the Controller.
8. Create Our First Pipeline
Let us reuse the small "Hello, World!" Pipeline from Part 1.
From the Jenkins dashboard:
- Choose New Item.
- Enter the name
hello-jenkins. - Select Pipeline.
- Choose OK.
- Scroll to the Pipeline section.
- Select Pipeline script.
- Paste the following Jenkinsfile.
pipeline { agent any stages { stage('Build') { steps { sh 'echo "building..."' } } stage('Test') { steps { sh 'echo "testing..."' } } stage('Package') { steps { sh 'echo "hello Jenkins" > artifact.txt' archiveArtifacts artifacts: 'artifact.txt' } } } } Save the job and choose Build Now.
This is the point where the diagram from Part 1 becomes a running system.
9. Observe the Pipeline Lifecycle
Do not look only at the final green check mark. The purpose of this lab is to inspect what Jenkins does along the way.
9.1 Build request and Queue
When you choose Build Now, the Controller creates a build request.
If an Executor is immediately available, the Queue stage may be so short that you barely notice it. That does not mean the Queue was irrelevant; it simply means demand was matched with execution capacity immediately.
Conceptually:
9.2 Executor
Watch Build Executor Status on the Jenkins UI while the job is running.
The build is assigned to one of the Executors on the built-in node. That Executor is the execution slot described in Part 1.
The EC2 instance is the machine. The built-in node represents Jenkins' view of the execution environment. The Executor is the slot that runs this particular workload.
9.3 Console Output
Open the running build and select Console Output.
You should see output corresponding to the Pipeline stages:
building... testing... You will also see Jenkins Pipeline bookkeeping around those commands.
The distinction matters: the Controller orchestrates the Pipeline, while the sh steps are workload execution.
9.4 Workspace
While the build is running, or after it finishes, inspect the Workspace from the server:
sudo ls -lah /var/lib/jenkins/workspace/ You should see a directory for the job, for example:
/var/lib/jenkins/workspace/hello-jenkins Inspect it:
sudo ls -lah /var/lib/jenkins/workspace/hello-jenkins The file created by the Package stage should appear in the Workspace: artifact.txt
This is exactly why the Workspace is not the same thing as Jenkins state. It is a working directory for the build.
9.5 Archived Artifact
Return to the completed build in the Jenkins UI.
Because the Pipeline contains:
archiveArtifacts artifacts: 'artifact.txt' Jenkins records the file as an archived Artifact.
The same file therefore participates in two different lifecycles:
That difference becomes critical once Agents become disposable.
10. Restart Jenkins and Verify Persistence
We have already said that Jenkins is stateful. Let us verify it.
Restart the Jenkins service:
sudo systemctl restart jenkins Wait a few seconds and check:
sudo systemctl status jenkins --no-pager Refresh the browser.
The hello-jenkins job and its build history should still exist because they are stored under JENKINS_HOME on persistent EBS storage.
This is a small experiment, but it demonstrates the difference between process lifetime and state lifetime:
Restarting a process is not the same as rebuilding Jenkins from nothing.
At the same time, EBS persistence alone is not a backup or a disaster-recovery strategy. We will treat those separately later in the series.
11. Verify What You Built
Before moving on, make sure you can verify the core concepts from your own environment:
- [ ] Jenkins is running as a
systemdservice with Java 21. - [ ] You can locate
JENKINS_HOMEat/var/lib/jenkins. - [ ] The Pipeline runs on the built-in node through an Executor.
- [ ] You can locate the
hello-jenkinsWorkspace andartifact.txt. - [ ] The archived Artifact is visible in the Jenkins UI.
- [ ] The job and build history survive a Jenkins service restart.
- [ ] You can explain why running builds on the Controller is only a temporary learning design.
If you can verify these points, the mental model from Part 1 is now connected to a real Jenkins installation.
12. Cost and Cleanup
This lab incurs normal EC2 and EBS charges while the resources exist.
If you plan to continue directly to Part 3, keep the Jenkins Controller because we will reuse it and attach a separate static EC2 Agent.
If you are pausing the series, you can stop the EC2 instance to stop compute charges, but the EBS volume continues to incur storage charges.
If you no longer need the lab at all, terminate the instance and confirm whether the root EBS volume is configured to be deleted on termination.
Do not delete the environment if you intend to continue to Part 3.
Key Takeaways
1. Jenkins is now a real Linux service.
The Controller runs as a Java process managed by systemd on an EC2 instance.
2. Jenkins state in JENKINS_HOME is persistent.
In this baseline, /var/lib/jenkins lives on EBS, so Jenkins state survives a service restart or instance reboot as long as the volume remains intact. Persistence, however, is not the same as backup or disaster recovery.
3. The Pipeline lifecycle from Part 1 is now observable.
A build moves through the Controller, Queue, Executor allocation, Workspace execution, Console Output, and Artifact handling.
4. The built-in node couples orchestration and build execution.
The Controller and build workload share CPU, memory, disk, and the same failure domain. A problematic build can therefore affect the Controller itself.
5. Direct port 8080 access and manual installation are learning shortcuts.
They make the platform easy to inspect, but they are not the ingress, security, or automation model we ultimately want for production.
6. The all-in-one EC2 architecture is a baseline, not a mistake.
Its purpose is to expose the limitations that justify the next step: moving build execution away from the Controller and onto Jenkins Agents.
Next: Part 3 — Adding Static Jenkins Agents on AWS EC2
We now have a working Jenkins Controller, but it is still executing the build workload itself. In Part 3 — Adding Static Jenkins Agents on AWS EC2, we will launch a second EC2 instance, connect it as a static Jenkins Agent, and use labels and Executors to move Pipeline execution away from the Controller.
That separation will let the Controller focus on orchestration while build workloads run on independently managed compute—the first meaningful step from a learning installation toward a scalable Jenkins platform.
References and Version Notes
This lab follows the Jenkins project's current Linux installation guidance and Java support policy. The commands shown here use the Jenkins stable Debian repository and Java 21.
If you are following the series long after publication, verify the supported Java version and Jenkins repository signing key against the current Jenkins documentation before installing.














