A free, guided curriculum

Learn Jenkins by
building a real pipeline.

Seven phases, start to finish: install Jenkins, write your first Jenkinsfile, containerize an app with Docker, provision infrastructure with Terraform, configure it with Ansible, and deploy on AWS — then lock it down like a production system. No prior DevOps experience required.

Stage View

01 Core Concepts
02 Jobs & Builds
03 Source Control
04 Pipelines
05 Docker
06 Terraform, Ansible & AWS
07 Production Practices

Phase 1 of 7

Core Concepts

Install Jenkins, learn the architecture, run your first job.

Goal of this phase: Understand what Jenkins is, how it's structured, install it two ways, and run your very first build.


1. What is Jenkins? (Concept)

Jenkins is an open-source automation server. Its main job is to automate the boring, repetitive parts of software delivery:

  • Pulling code from a repo (GitHub, GitLab, etc.)
  • Running builds/tests
  • Packaging artifacts (JARs, Docker images, zip files)
  • Deploying to servers, cloud environments, or Kubernetes

It's the "orchestrator" that sits between your code and your infrastructure — very similar in spirit to how you already use Ansible to orchestrate configuration, except Jenkins orchestrates the pipeline of steps, and can call Ansible, Terraform, Docker, AWS CLI, etc. as steps inside it.

Why it matters for you

You already know Terraform (infra), Ansible (config), Docker (packaging), AWS (deployment target). Jenkins is the glue that triggers and sequences all of those automatically whenever code changes — instead of you running commands manually.


2. Jenkins Architecture (Concept)

shell
                ┌─────────────────────────┐
                │   Jenkins Controller     │  (formerly called "Master")
                │  - Web UI                │
                │  - Job/Pipeline configs  │
                │  - Scheduler             │
                │  - Plugin manager        │
                └───────────┬─────────────┘
                            │ dispatches work
              ┌─────────────┼─────────────┐
              ▼             ▼             ▼
        ┌──────────┐  ┌──────────┐  ┌──────────┐
        │  Agent 1 │  │  Agent 2 │  │  Agent 3 │   (formerly "Slaves")
        │(executor)│  │(executor)│  │(executor)│
        └──────────┘  └──────────┘  └──────────┘
Term Meaning
Controller (Master) The main Jenkins server. Hosts the UI, stores job configs, schedules builds.
Agent (Node) A machine (physical/VM/container) that actually executes the build steps. Controller delegates work to agents.
Executor A slot on an agent that can run one build at a time. An agent can have multiple executors.
Job / Project A configured task Jenkins can run (e.g., "build my app").
Build One execution/run of a job.
Workspace The directory on the agent where Jenkins checks out code and runs the job.
Plugin Add-on that extends Jenkins (Git integration, Docker, Slack notifications, etc.)

For learning purposes, you'll run everything on a single machine where the controller also acts as an agent (built-in node) — that's completely normal for a lab setup.


3. Installation — Two Methods (Do Both)

You'll install Jenkins twice, since you specifically want the practice: once via Docker (fast, disposable, great for experimenting) and once natively on an EC2 instance (closer to how real production Jenkins servers are often run).

Method A: Jenkins via Docker (do this first — fastest)

Prerequisite: Docker installed on your machine (you already have this skill).

bash
# 1. Pull and run Jenkins LTS (Long Term Support) image
docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts

# 2. Check it's running
docker ps

# 3. Get the initial admin password (needed for first login)
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
  • Port 8080 → Jenkins web UI
  • Port 50000 → used for agent-controller communication (JNLP agents)
  • The -v jenkins_home:/var/jenkins_home volume means your Jenkins data persists even if you stop/remove the container — important, don't skip this.

Now open http://localhost:8080 (or http://<your-ec2-public-ip>:8080 if running on an EC2 instance — remember to open port 8080 in the Security Group).

Method B: Jenkins natively on EC2 (Amazon Linux 2023 / RHEL-based)

Since you already work with EC2 and Red Hat Satellite, this will feel familiar.

bash
# 1. Launch an EC2 instance (t2.medium recommended — Jenkins needs at least 2GB RAM)
#    Open inbound port 8080 (Jenkins UI) and 22 (SSH) in the Security Group

# 2. SSH into the instance
ssh -i your-key.pem ec2-user@<public-ip>

# 3. Install Java (Jenkins requires Java 17 or 21 as of recent LTS versions)
sudo yum install -y java-17-amazon-corretto

# 4. Add the Jenkins repo and import the GPG key
sudo wget -O /etc/yum.repos.d/jenkins.repo \
  https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key

# 5. Install Jenkins
sudo yum install -y jenkins

# 6. Start and enable Jenkins as a service
sudo systemctl start jenkins
sudo systemctl enable jenkins
sudo systemctl status jenkins

# 7. Get the initial admin password
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

Open http://<ec2-public-ip>:8080 in your browser.

Compare the two methods — this comparison itself is a learning exercise: | | Docker | Native (EC2/yum) | |---|---|---| | Setup speed | Fastest | Slower, more steps | | Data location | Docker volume | /var/lib/jenkins | | Managed as | Container | systemd service | | Realistic for prod? | Yes (common in modern setups) | Yes (classic setup, still very common in enterprises) |


4. First-Time Setup Wizard

  1. Paste the initial admin password into the browser prompt.
  2. Choose "Install suggested plugins" (recommended for learning — installs Git, Pipeline, Credentials Binding, and other essentials automatically).
  3. Create your first admin user (don't skip this — set a real username/password, not the default admin account).
  4. Confirm the Jenkins URL (leave default: http://localhost:8080/ or your EC2 IP).
  5. Click Start using Jenkins → you land on the Dashboard.

5. Explore the Dashboard (Concept + Hands-on)

Spend 10-15 minutes clicking through these before creating a job:

  • Manage Jenkins → central hub for plugins, system config, security, nodes
  • Manage Jenkins → Plugins → Available/Installed/Updates tabs
  • Manage Jenkins → Nodes → see your built-in node (controller acting as agent), note the number of executors
  • New Item → where you create jobs/pipelines
  • People → user management
  • Build History (appears once you run jobs) → left sidebar

Install these plugins now (Manage Jenkins → Plugins → Available plugins)

If not already installed via "suggested plugins": - Git plugin — pulls code from Git repos - Pipeline plugin — for Jenkinsfile-based pipelines (Phase 4) - Docker Pipeline — will need this in Phase 5 - Blue Ocean (optional) — nicer pipeline visualization UI


6. Project: Your First Freestyle Job

Objective: Create a job that pulls a public GitHub repo and runs a shell script — a "Hello World" for Jenkins.

Step-by-step

  1. Click New Item
  2. Enter name: hello-world-freestyle
  3. Select Freestyle project → OK
  4. Under Source Code Management, select Git - Repository URL: use any public repo, e.g. https://github.com/octocat/Hello-World.git - Leave branch as */master
  5. Under Build Triggers, leave unchecked for now (we'll do this manually first)
  6. Under Build Steps, click Add build step → Execute shell (Linux) and add: bash echo "Hello from Jenkins!" echo "Current directory contents:" ls -la echo "Build number: $BUILD_NUMBER" echo "Job name: $JOB_NAME"
  7. Click Save
  8. Click Build Now (left sidebar)
  9. Under Build History, click the build number (e.g., #1) → Console Output
  10. Read the console output — this is your build log. Confirm you see your echoed text and the repo files listed.

What just happened: Jenkins cloned the Git repo into a workspace, ran your shell commands inside that workspace, and logged everything. This is the foundation every pipeline builds on.

Stretch goals (optional, once the above works)

  • Change the build trigger to "Poll SCM" with schedule H/5 * * * * (checks the repo every 5 minutes for changes)
  • Add a Parameterized Build (Boolean or String parameter) and reference it in your shell step using $PARAM_NAME
  • Try "Build periodically" trigger with cron syntax, e.g. H 9 * * * (once daily around 9 AM)

7. Checkpoint — Can You Answer These?

Before moving to Phase 2, make sure you can explain (out loud or in writing, no notes):

  1. What's the difference between the Jenkins controller and an agent?
  2. What is an executor?
  3. What is a workspace, and where does it live?
  4. What's the difference between a job and a build?
  5. Why would you choose Docker vs native install for Jenkins in a real company?
  6. What does the initialAdminPassword file do, and why is it only needed once?

If you can answer all six clearly, you're ready for Phase 2: Jobs & Builds (build triggers, parameterized builds, post-build actions, artifact archiving).


Quick Reference — Useful Commands

bash
# Docker Jenkins
docker start jenkins
docker stop jenkins
docker logs jenkins
docker exec -it jenkins bash

# Native Jenkins (systemd)
sudo systemctl restart jenkins
sudo systemctl status jenkins
sudo journalctl -u jenkins -f       # live logs
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

Next: Phase 2 — Jobs & Builds (triggers, parameters, post-build actions). Ask me when you're ready and I'll build the same style of guide for it.

Start of curriculum

Phase 2 of 7

Jobs & Builds

Triggers, parameters, environment variables, post-build actions.

Goal of this phase: Learn how to make Jenkins jobs run automatically, accept inputs, and act on results — the day-to-day mechanics of running builds.

Assumes only what you learned in Phase 1 (installing Jenkins, creating a Freestyle job, running "Build Now"). No AWS/Terraform/Ansible knowledge assumed here — everything is plain Jenkins + shell commands.


1. Build Triggers (Concept)

In Phase 1, you clicked "Build Now" manually every time. In real use, you rarely do that — you want Jenkins to run builds automatically. This is what a Build Trigger controls.

Trigger What it does When to use
Manual (Build Now) You click the button yourself Testing, one-off runs
Poll SCM Jenkins checks the repo on a schedule; if something changed, it builds Simple setups, no webhook access
GitHub hook trigger (Webhook) GitHub pings Jenkins the instant code is pushed Best practice — instant, efficient
Build periodically Runs on a fixed schedule regardless of code changes Nightly builds, scheduled reports/cleanup jobs
Trigger builds remotely (API) Another system/script calls a URL to start the build Chained automation, external tools
Build after other projects Runs after another specific job finishes Multi-job pipelines (before you learn real Pipelines)

What is "SCM"?

SCM = Source Control Management — just a general term for systems like Git, GitHub, GitLab, Bitbucket that track versions of your code. When Jenkins docs say "SCM," they usually just mean "your Git repo."

Cron syntax (used in Poll SCM and Build Periodically)

Jenkins uses a 5-field cron-like format:

shell
MINUTE HOUR DAY_OF_MONTH MONTH DAY_OF_WEEK

Examples: - H/5 * * * * → every 5 minutes (the H means Jenkins picks a random offset to spread load — this is Jenkins-specific, not standard cron) - H 9 * * * → once a day, around 9 AM - H 9 * * 1-5 → once a day, around 9 AM, Monday–Friday only - 0 0 * * 0 → exactly midnight every Sunday


2. Hands-On: Add a Real Trigger to Your Phase 1 Job

  1. Open your hello-world-freestyle job → Configure
  2. Under Build Triggers, check Poll SCM
  3. Enter schedule: H/5 * * * *
  4. Save
  5. Go to your GitHub repo (or the public repo you used), and just wait — or make a small change if it's your own repo — then check back in ~5 minutes to see if a new build triggered automatically. You can also check Manage Jenkins → System Log or the job's build history to confirm it polled.

Note: Poll SCM is a fine learning tool, but it's inefficient in real companies (constant polling wastes resources). The professional approach is a GitHub webhook, which we'll set up properly in Phase 3 once you have a repo you control and Jenkins is reachable from the internet.


3. Parameterized Builds (Concept)

A parameter lets you pass different input values into the same job each time you run it — instead of hardcoding everything. Think of it like passing arguments into a function.

Common parameter types: - String Parameter — free text (e.g., a version number, a branch name) - Choice Parameter — dropdown with fixed options (e.g., dev, staging, prod) - Boolean Parameter — checkbox, true/false (e.g., "run tests? yes/no") - Password Parameter — hidden text input (rarely used now — Credentials store is safer, covered in Phase 3)

Hands-On: Add Parameters

  1. Open hello-world-freestyleConfigure
  2. Check This project is parameterized
  3. Click Add Parameter → Choice Parameter - Name: ENVIRONMENT - Choices (one per line): dev staging prod
  4. Click Add Parameter → Boolean Parameter - Name: RUN_TESTS - Default value: checked
  5. Scroll to your Execute shell build step and update it to: bash echo "Deploying to environment: $ENVIRONMENT" if [ "$RUN_TESTS" = "true" ]; then echo "Running tests..." else echo "Skipping tests." fi
  6. Save. Notice the job no longer has a plain "Build Now" button — it now says "Build with Parameters".
  7. Click it, choose an environment from the dropdown, toggle the checkbox, and run it.
  8. Check Console Output — confirm your choices were reflected in the log.

What just happened: You made one job flexible enough to behave differently based on input, instead of writing three separate jobs for dev/staging/prod. This is the beginning of "one pipeline, many environments" thinking.


4. Environment Variables (Concept)

Jenkins automatically injects certain variables into every build. You already saw a couple in Phase 1 ($BUILD_NUMBER, $JOB_NAME). Some other useful built-in ones:

Variable Meaning
$BUILD_NUMBER Sequential number of this run (1, 2, 3...)
$JOB_NAME Name of the job
$BUILD_ID Same as build number (older format)
$WORKSPACE Full path to the job's working directory on the agent
$JENKINS_URL The base URL of your Jenkins server
$GIT_COMMIT Commit hash that was checked out (only if using Git SCM)
$GIT_BRANCH Branch that was checked out

Try adding this to your shell step to explore:

bash
echo "Workspace path: $WORKSPACE"
echo "Git commit: $GIT_COMMIT"
echo "Git branch: $GIT_BRANCH"

5. Post-Build Actions (Concept)

A post-build action runs after your main build steps finish — regardless of whether the build succeeded or failed (depending on which action you pick). Common ones:

  • Archive the artifacts — saves specific output files (e.g., a zip, a log, a report) so you can download them later from the build page, instead of them disappearing when the workspace gets cleaned
  • E-mail Notification — sends an email on failure/success (needs SMTP configured under Manage Jenkins → System, skip for now unless you want to set up a test mail server)
  • Trigger a downstream job — start another job once this one finishes ("Build other projects")

Hands-On: Archive an Artifact

  1. Open hello-world-freestyleConfigure
  2. In the Execute shell step, add a line that creates a file: bash echo "Build $BUILD_NUMBER completed for $ENVIRONMENT" > result.txt
  3. Scroll down to Post-build Actions → Add post-build action → Archive the artifacts
  4. In Files to archive, enter: result.txt
  5. Save, then Build with Parameters again
  6. Go to the completed build page (e.g., #5) — you'll now see a "Build Artifacts" section with result.txt listed as a downloadable file

This is exactly how, later, you'll archive things like test reports or a packaged application.


6. Build History & Workspace Management (Concept)

  • Build History (left sidebar of a job) — every past run, color-coded: 🟢 blue/green = success, 🔴 red = failed, 🟡 yellow = unstable (tests failed but build didn't crash)
  • Workspace — click "Workspace" on the job's sidebar to browse files currently sitting in that job's working directory on the agent. This is useful for debugging — "what files does Jenkins actually see when it runs?"
  • Jenkins keeps build history/logs based on rules you can set under Discard old builds (Configure → General) — e.g., "keep only the last 10 builds" — important in real setups so disk doesn't fill up.

Hands-On: Limit Build History

  1. Configure → General → check Discard old builds
  2. Set Max # of builds to keep: 10
  3. Save

7. Checkpoint — Can You Answer These?

  1. What's the difference between Poll SCM and a webhook trigger, and why is a webhook usually better?
  2. What are the three parameter types you used, and what's each one good for?
  3. Name two built-in Jenkins environment variables and what they contain.
  4. What does "archiving an artifact" actually do, and why does it matter?
  5. Why would a company configure "Discard old builds"?

If you can answer all five without checking notes, you're ready for Phase 3: Source Control Integration (properly connecting Jenkins to GitHub with webhooks, and managing credentials securely instead of pasting tokens into scripts).


Quick Reference — This Phase

bash
# Cron examples for triggers
H/5 * * * *      # every ~5 minutes
H 9 * * *        # once daily, ~9 AM
H 9 * * 1-5      # once daily, weekdays only

# Useful Jenkins built-in variables inside a shell step
$BUILD_NUMBER
$JOB_NAME
$WORKSPACE
$GIT_COMMIT
$GIT_BRANCH

Next: Phase 3 — Source Control Integration (GitHub webhooks, credentials management). Ask me when you're ready.

Phase 3 of 7

Source Control

GitHub webhooks and secure credentials management.

Goal of this phase: Connect Jenkins to a real GitHub repo you control, trigger builds instantly via webhooks instead of polling, and manage credentials the secure way instead of hardcoding secrets.

No AWS/Terraform/Ansible needed. You'll need a free GitHub account and a repo you own (not just a public repo you don't control, like in Phase 1).


1. Why This Phase Matters

So far, Jenkins has pulled from a public repo you don't own, and you've triggered builds manually or via polling. In real jobs, Jenkins needs to: - React instantly when your team pushes code (webhook, not polling) - Authenticate to private repos, Docker registries, cloud accounts — without secrets sitting in plain text anywhere

This phase fixes both.


2. Set Up a Repo You Control

  1. Log into GitHub (create a free account if you don't have one: github.com)
  2. Click New repository - Name: jenkins-practice - Visibility: Public (simpler for webhooks; private works too but needs extra credential setup, covered in section 5) - Check Add a README
  3. Clone it to your machine and add a simple file: bash git clone https://github.com/<your-username>/jenkins-practice.git cd jenkins-practice echo "echo 'Hello from my own repo!'" > build.sh git add build.sh git commit -m "Add build script" git push

You now own a repo Jenkins can watch for changes.


3. GitHub Webhooks (Concept)

Polling (Phase 2) = Jenkins repeatedly asks GitHub "anything new?" every few minutes — wasteful and slow.

Webhook = GitHub itself sends Jenkins a message ("something changed!") the instant you push code. Jenkins reacts immediately. This is the standard, professional setup.

For GitHub to reach your Jenkins server, Jenkins must be reachable from the internet (a public IP/URL). This is one reason production Jenkins runs on cloud servers rather than a home laptop.

  • If you installed Jenkins on EC2 (Phase 1, Method B), it already has a public IP — perfect for this.
  • If you're running Jenkins via Docker on your local machine, GitHub can't reach localhost. You have two options:
  • Use your EC2-hosted Jenkins instead for this phase, or
  • Use a tunneling tool like ngrok to expose your local Jenkins temporarily (quick option, good for practice): bash # Install ngrok, then: ngrok http 8080 This gives you a temporary public URL like https://abcd1234.ngrok-free.app that forwards to your local Jenkins.

Hands-On: Configure the Webhook

  1. In GitHub, go to your jenkins-practice repo → Settings → Webhooks → Add webhook
  2. Payload URL: http://<your-jenkins-url>:8080/github-webhook/ (must end in /github-webhook/ exactly)
  3. Content type: application/json
  4. Which events: "Just the push event" is fine for now
  5. Click Add webhook — GitHub will do a test ping; check it shows a green checkmark (✅) under "Recent Deliveries"

Hands-On: Configure Jenkins to Use the Webhook

  1. Create a new Freestyle job (or reuse an existing one): hello-world-webhook
  2. Under Source Code Management → Git, enter: https://github.com/<your-username>/jenkins-practice.git
  3. Under Build Triggers, check GitHub hook trigger for GITScm polling (note: different checkbox from "Poll SCM")
  4. Add a build step: Execute shell: bash chmod +x build.sh ./build.sh
  5. Save
  6. Make a small change in your repo and push it: bash echo "# test change" >> README.md git add README.md git commit -m "Trigger webhook test" git push
  7. Within a few seconds, check Jenkins — a new build should have started automatically, no polling delay.

If it doesn't trigger: check GitHub's webhook "Recent Deliveries" tab for the response code Jenkins sent back — a non-200 response tells you exactly what failed (wrong URL, Jenkins unreachable, etc.). This is the standard way to debug webhook issues.


4. Credentials Management (Concept)

The problem: Jobs often need secrets — a GitHub token to access a private repo, a password, an API key. Pasting these directly into shell scripts or job configs is a security risk (visible in logs, in job config XML, to anyone with Jenkins access).

The solution: Jenkins has a built-in Credentials Store — a secure vault. You store the secret once, give it an ID, and reference that ID in jobs. Jenkins masks the actual value in console output automatically.

Types of credentials Jenkins supports

Type Used for
Username with password Basic auth to services
SSH Username with private key Git over SSH, connecting to remote servers
Secret text API tokens, single secret strings
Secret file Config files, .pem keys, kubeconfig files
Certificate Client certs (X.509)

Hands-On: Store a GitHub Personal Access Token

  1. In GitHub: Settings (your profile, not the repo) → Developer settings → Personal access tokens → Tokens (classic) → Generate new token - Give it repo scope - Copy the token immediately (shown only once)
  2. In Jenkins: Manage Jenkins → Credentials → System → Global credentials → Add Credentials - Kind: Username with password - Username: your GitHub username - Password: paste the token (NOT your actual GitHub password — tokens are safer and revocable) - ID: github-token (this is the reference name you'll use in jobs)
  3. Save

Hands-On: Use the Credential in a Job

  1. Open a job → Configure → Source Code Management → Git
  2. Under Credentials, select the dropdown → your github-token entry should now appear
  3. This lets Jenkins clone private repos, or push commits back, without you ever typing the token into a script

Try it: Make your jenkins-practice repo private (GitHub repo Settings → Change visibility), then confirm the job still works because it now authenticates via the stored credential instead of relying on public access.


5. Using Credentials Inside Build Steps

Sometimes you need to use a secret inside a shell command (e.g., an API key for a script), not just for Git checkout.

  1. Configure a job → Build Environment → check Use secret text(s) or file(s)
  2. Bind a credential to an environment variable, e.g., variable name MY_TOKEN bound to a Secret text credential
  3. In your shell step, reference $MY_TOKEN — Jenkins injects it at runtime and masks it in the console log (shows as ****)

This is the safe pattern you'll reuse constantly once you get to Docker registry logins, cloud credentials, etc.


6. Checkpoint — Can You Answer These?

  1. Why is a webhook better than Poll SCM, in your own words?
  2. Why can't GitHub send a webhook to a Jenkins running on localhost without something like ngrok?
  3. What's the difference between "Username with password" and "Secret text" credential types — when would you use each?
  4. Why shouldn't you paste an API token directly into a shell script build step?
  5. What does Jenkins do automatically to protect a secret value in the console output?

If you can answer all five, you're ready for Phase 4: Jenkins Pipelines — this is the big one, where you'll write your first Jenkinsfile and move from clicking around the UI to pipeline-as-code, which is how virtually all real-world Jenkins usage works.


Quick Reference — This Phase

shell
Webhook payload URL format:
http://<jenkins-host>:8080/github-webhook/

ngrok (expose local Jenkins temporarily):
ngrok http 8080

Credential reference pattern:
Store secret once with an ID → reference that ID in job config
→ Jenkins injects + masks it at runtime

Next: Phase 4 — Jenkins Pipelines (Jenkinsfile, declarative syntax, stages). Ask me when you're ready.

Phase 4 of 7

Pipelines

Jenkinsfile, declarative syntax, parallel stages, approval gates.

Goal of this phase: Move from clicking around the UI (Freestyle jobs) to writing pipeline-as-code with a Jenkinsfile — this is how virtually all real-world Jenkins usage works, and it's the core skill interviewers will test.

No AWS/Terraform/Ansible needed yet. Builds directly on the jenkins-practice repo and credentials you set up in Phase 3.


1. Why Pipelines Instead of Freestyle Jobs? (Concept)

Everything you did in Phases 1–3 was configured by clicking through the Jenkins UI. That configuration lives inside Jenkins itself — it's not version-controlled, not easily reviewed, and not portable.

A Pipeline flips this: you write the entire build process as code in a file called a Jenkinsfile, which you commit to your repo alongside your actual project. Benefits: - Version-controlled (see history, diffs, who changed what) - Code review possible (pull requests on your CI/CD process itself) - Portable (works the same across any Jenkins instance) - Supports much more complex logic than Freestyle jobs allow (parallel stages, conditional logic, retries)

This is the single most important shift in this whole learning path. Everything after this phase builds on it.


2. Declarative vs Scripted Pipeline (Concept)

Jenkins supports two pipeline syntaxes:

Declarative Scripted
Syntax style Structured, predictable blocks (pipeline { stages { ... } }) Full Groovy programming language, very flexible
Learning curve Easier, recommended for most use cases Steeper, needed for advanced edge cases
Readability Easier for teams to read/review Can get complex quickly
What you'll learn here This one — used in ~90% of real Jenkinsfiles Mentioned for awareness only

We'll focus entirely on Declarative Pipeline syntax — it's what you'll use almost all the time in real jobs.


3. Anatomy of a Jenkinsfile (Concept)

groovy
pipeline {
    agent any                    // Where this runs (which agent/node)

    environment {
        GREETING = "Hello"       // Environment variables available to all stages
    }

    stages {
        stage('Build') {         // A named phase of the pipeline
            steps {
                sh 'echo "Building..."'
            }
        }
        stage('Test') {
            steps {
                sh 'echo "Testing..."'
            }
        }
        stage('Deploy') {
            steps {
                sh 'echo "Deploying..."'
            }
        }
    }

    post {                       // Runs after all stages, regardless of outcome
        success {
            echo 'Pipeline succeeded!'
        }
        failure {
            echo 'Pipeline failed!'
        }
        always {
            echo 'This always runs.'
        }
    }
}

Key blocks explained

Block Purpose
pipeline { } Wraps the entire thing — every declarative Jenkinsfile starts here
agent Where the pipeline executes. agent any = run on any available agent. Can be scoped per-stage too.
environment { } Defines variables usable throughout the pipeline via $VAR_NAME
stages { } Container for all your named stages — the visual "steps" you'll see in the Jenkins UI
stage('Name') { } One logical phase (Build, Test, Deploy, etc.) — shows up as a box in the pipeline visualization
steps { } The actual commands/actions inside a stage
sh Runs a shell command (use bat instead if you're on Windows agents)
post { } Cleanup/notification logic — runs after the pipeline, with conditions like success, failure, always, unstable

4. Hands-On: Your First Jenkinsfile

  1. In your jenkins-practice repo (from Phase 3), create a file named exactly Jenkinsfile (no extension) in the root: bash cd jenkins-practice
  2. Create the file with this content: ```groovy pipeline { agent any

    environment { GREETING = "Hello from my first Jenkinsfile" }

    stages { stage('Build') { steps { echo "${GREETING}" sh 'echo "Simulating build step..."' } } stage('Test') { steps { sh 'echo "Simulating tests..."' } } stage('Deploy') { steps { sh 'echo "Simulating deploy step..."' } } }

    post { success { echo 'All stages completed successfully!' } failure { echo 'Something went wrong.' } } } 3. Commit and push:bash git add Jenkinsfile git commit -m "Add first Jenkinsfile" git push `` 4. In Jenkins, click **New Item** - Name:hello-world-pipeline- Type: **Pipeline** (not Freestyle this time) 5. Under **Pipeline** section at the bottom of the config page: - Definition: **Pipeline script from SCM** - SCM: **Git** - Repository URL: yourjenkins-practicerepo URL - Credentials: select yourgithub-tokencredential from Phase 3 if the repo is private - Script Path:Jenkinsfile` (default, matches the filename you created) 6. Save → Build Now 7. Watch the Stage View — you'll see boxes for Build/Test/Deploy light up green as each completes. This visualization is one of the biggest advantages of Pipelines over Freestyle jobs. 8. Click into the build → Console Output to see the full log, same as before.

This is "Pipeline as Code from SCM" — Jenkins is now reading its instructions from your Git repo, not from UI clicks. If you change the Jenkinsfile and push, the next build automatically uses the new logic — no need to touch Jenkins config at all.


5. Parameters, Conditionals, and when (Concept + Hands-On)

Declarative pipelines support parameters and conditional stage execution, similar to what you did in Phase 2 but as code.

Update your Jenkinsfile:

groovy
pipeline {
    agent any

    parameters {
        choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'prod'], description: 'Target environment')
        booleanParam(name: 'RUN_TESTS', defaultValue: true, description: 'Run tests?')
    }

    stages {
        stage('Build') {
            steps {
                echo "Building for ${params.ENVIRONMENT}"
            }
        }
        stage('Test') {
            when {
                expression { params.RUN_TESTS == true }
            }
            steps {
                sh 'echo "Running tests..."'
            }
        }
        stage('Deploy to Prod') {
            when {
                expression { params.ENVIRONMENT == 'prod' }
            }
            steps {
                sh 'echo "Deploying to PRODUCTION - be careful!"'
            }
        }
    }
}

Commit, push, then run Build with Parameters on your pipeline job. Try different combinations: - ENVIRONMENT=dev, RUN_TESTS=false → Test stage should be skipped (shown gray in Stage View, not run) - ENVIRONMENT=prod, RUN_TESTS=true → All stages run, including "Deploy to Prod"

This when { expression { ... } } pattern is exactly how real pipelines decide "only deploy to prod if this is the main branch" or "only run this stage on Fridays," etc.


6. Parallel Stages (Concept + Hands-On)

Sometimes stages don't depend on each other and can run at the same time to save time (e.g., running unit tests and linting simultaneously).

groovy
stage('Parallel Checks') {
    parallel {
        stage('Unit Tests') {
            steps {
                sh 'echo "Running unit tests..."'
            }
        }
        stage('Lint Check') {
            steps {
                sh 'echo "Running lint check..."'
            }
        }
    }
}

Add this as a new stage in your Jenkinsfile, push, and rebuild. In the Stage View, you'll see both boxes execute side-by-side instead of one after another — this is a real time-saver on larger real-world pipelines.


7. Manual Approval Gates with input (Concept + Hands-On)

For risky steps (like deploying to production), you often want a human to approve before Jenkins proceeds.

groovy
stage('Approval') {
    steps {
        input message: 'Deploy to production?', ok: 'Yes, deploy'
    }
}
stage('Deploy to Prod') {
    steps {
        sh 'echo "Deploying to production now..."'
    }
}

Add this before your prod deploy stage, push, and rebuild. The pipeline will pause and show a clickable prompt in the Jenkins UI (and on the build page) waiting for someone to click "Yes, deploy" or abort. This is a critical real-world safety pattern — you'll use it constantly once deployments matter.


8. Checkpoint — Can You Answer These?

  1. What is a Jenkinsfile, and where does it live?
  2. What's the practical difference between Declarative and Scripted pipeline syntax, and which should you default to?
  3. What does agent any mean?
  4. How do you make a stage run only under certain conditions?
  5. What does parallel { } do, and when would you use it?
  6. What does the input step do, and why is it useful before a production deploy?
  7. What does "Pipeline script from SCM" mean, and how is it different from pasting a pipeline script directly into the Jenkins UI?

If you can answer all seven, you're ready for Phase 5: Integrations with your existing stack — this is where Jenkins pipelines start calling Docker, and (once you're ready to learn them) Terraform, Ansible, and AWS. Since you said not to assume that knowledge yet, just let me know when you want Phase 5, and I'll either build it Docker-only for now, or fold in a "crash course" primer on Terraform/Ansible/AWS basics first — your call.


Quick Reference — This Phase

groovy
pipeline {
    agent any
    parameters {
        choice(name: 'X', choices: ['a','b'], description: '')
        booleanParam(name: 'Y', defaultValue: true, description: '')
    }
    stages {
        stage('Name') {
            when { expression { params.Y == true } }
            steps { sh 'command' }
        }
        stage('Parallel Example') {
            parallel {
                stage('A') { steps { sh 'echo A' } }
                stage('B') { steps { sh 'echo B' } }
            }
        }
        stage('Gate') {
            steps { input message: 'Proceed?', ok: 'Go' }
        }
    }
    post {
        success { echo 'OK' }
        failure { echo 'Failed' }
    }
}

Next: Phase 5 — Integrations with Docker (and optionally Terraform/Ansible/AWS with a primer first, your choice). Ask me when you're ready.

Phase 5 of 7

Docker

Build and push Docker images from a pipeline.

Goal of this phase: Get Jenkins building and pushing Docker images as part of a pipeline. This is the most common real-world use of Jenkins today — package an app into a container image automatically on every push.

Assumption for this phase: Docker-only, no Terraform/Ansible/AWS required. (When you're ready to add cloud deployment, let me know and I'll either give you a primer first or fold it into a later phase.)

Builds on Phase 4 — you'll extend the same jenkins-practice Jenkinsfile.


1. Why Docker + Jenkins? (Concept)

Instead of just running shell commands, most real pipelines end with: "package this app as a Docker image, then push it somewhere (Docker Hub, AWS ECR, etc.) so it can be deployed anywhere." Jenkins automates that packaging + publishing step every time code changes.

Basic flow you're building today:

shell
Push code → Jenkins triggers → Jenkins builds Docker image → Jenkins pushes image to Docker Hub

2. Prerequisite: Give Jenkins Access to Docker

Jenkins needs the docker command available wherever the pipeline runs (the agent).

If Jenkins is running via Docker (Phase 1, Method A)

The Jenkins container needs access to the host's Docker daemon — this is done by mounting the Docker socket:

bash
# Stop and remove the old container (data is safe in the jenkins_home volume)
docker stop jenkins
docker rm jenkins

# Re-run with Docker socket mounted so Jenkins can build/run Docker images
docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -u root \
  jenkins/jenkins:lts

# Install the docker CLI inside the container
docker exec -u root jenkins apt-get update
docker exec -u root jenkins apt-get install -y docker.io

Note: -u root and mounting the Docker socket directly means the Jenkins container can control the host's Docker — convenient for learning, but a known security tradeoff in production (out of scope for now, just be aware it's a shortcut).

If Jenkins is running natively on EC2 (Phase 1, Method B)

Install Docker on the same EC2 instance, and give the jenkins user permission to use it:

bash
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker

# Add jenkins user to the docker group so it can run docker commands
sudo usermod -aG docker jenkins

# Restart Jenkins so the group change takes effect
sudo systemctl restart jenkins

Verify Jenkins can see Docker

Create a quick test Freestyle job (or reuse hello-world-freestyle) with build step:

bash
docker --version
docker ps

Run it — if you see version output and no permission errors, you're set.


3. Docker Hub Account + Credentials (Concept + Hands-On)

You need somewhere to push images. Docker Hub is the free, standard option.

  1. Create a free account at hub.docker.com if you don't have one
  2. Create an Access Token (Account Settings → Security → New Access Token) — safer than using your real password, and revocable
  3. In Jenkins: Manage Jenkins → Credentials → Global → Add Credentials - Kind: Username with password - Username: your Docker Hub username - Password: the access token - ID: dockerhub-creds
  4. Save

This follows the exact same pattern as the GitHub token credential you set up in Phase 3 — same mechanism, different service.


4. A Minimal App to Containerize

Add a tiny app to your jenkins-practice repo so there's something real to build.

bash
cd jenkins-practice

Create app.py:

python
print("Hello from my containerized app!")

Create Dockerfile:

dockerfile
FROM python:3.12-slim
COPY app.py /app.py
CMD ["python", "/app.py"]

Commit and push:

bash
git add app.py Dockerfile
git commit -m "Add minimal app and Dockerfile"
git push

5. Update the Jenkinsfile: Build & Push a Docker Image

Extend the Jenkinsfile from Phase 4:

groovy
pipeline {
    agent any

    environment {
        DOCKERHUB_CREDENTIALS = credentials('dockerhub-creds')
        IMAGE_NAME = "<your-dockerhub-username>/jenkins-practice"
        IMAGE_TAG = "${BUILD_NUMBER}"
    }

    stages {
        stage('Build') {
            steps {
                echo "Building image ${IMAGE_NAME}:${IMAGE_TAG}"
            }
        }

        stage('Docker Build') {
            steps {
                sh 'docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .'
            }
        }

        stage('Docker Login') {
            steps {
                sh 'echo $DOCKERHUB_CREDENTIALS_PSW | docker login -u $DOCKERHUB_CREDENTIALS_USR --password-stdin'
            }
        }

        stage('Docker Push') {
            steps {
                sh 'docker push ${IMAGE_NAME}:${IMAGE_TAG}'
            }
        }

        stage('Also tag latest') {
            steps {
                sh '''
                    docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest
                    docker push ${IMAGE_NAME}:latest
                '''
            }
        }
    }

    post {
        always {
            sh 'docker logout'
        }
        success {
            echo "Image pushed: ${IMAGE_NAME}:${IMAGE_TAG}"
        }
        failure {
            echo 'Docker build/push failed.'
        }
    }
}

What's new here, explained

Line What it does
credentials('dockerhub-creds') Pulls in the stored credential. Jenkins automatically creates DOCKERHUB_CREDENTIALS_USR and DOCKERHUB_CREDENTIALS_PSW variables from it.
${BUILD_NUMBER} as the tag Every build produces a uniquely tagged image — you can always trace an image back to the exact build that made it.
docker login ... --password-stdin Logs in without ever printing the password in plain text in a command (--password-stdin reads it from piped input instead of a CLI argument)
Tagging latest too Common convention — latest always points to the most recent successful build, while numbered tags let you roll back to any specific past version
post { always { docker logout } } Cleanup step — always log out afterward, regardless of success/failure

Replace <your-dockerhub-username> with your actual username, commit, push, then run the pipeline.


6. Verify

  1. Check the Jenkins Console Output — confirm each stage succeeded
  2. Go to hub.docker.com → your repositories → you should see jenkins-practice with at least two tags (latest and a build number like 3)
  3. Optionally pull and run it yourself to prove the whole loop worked: bash docker pull <your-dockerhub-username>/jenkins-practice:latest docker run <your-dockerhub-username>/jenkins-practice:latest You should see Hello from my containerized app! printed.

7. Checkpoint — Can You Answer These?

  1. Why does the Jenkins container need the Docker socket mounted (Docker-in-Docker approach), or the jenkins user added to the docker group (native install)?
  2. What does credentials('dockerhub-creds') do inside environment { }, and what two variables does it create automatically?
  3. Why tag each image with ${BUILD_NUMBER} instead of only ever using latest?
  4. Why use --password-stdin instead of putting the password directly in the docker login command?
  5. What's the difference between docker build, docker tag, and docker push?

If you can answer all five, you're ready for Phase 6: Production-Grade Practices — distributed agents, security/RBAC, backups, and monitoring, which wraps up the core Jenkins curriculum. After that, if you'd like, we can circle back and add Terraform/Ansible/AWS deployment stages on top of everything you've built.


Quick Reference — This Phase

bash
# Give Jenkins Docker access (Docker-based Jenkins)
-v /var/run/docker.sock:/var/run/docker.sock

# Give Jenkins Docker access (native Jenkins)
sudo usermod -aG docker jenkins

# Core Docker commands used in the pipeline
docker build -t <image>:<tag> .
docker login -u <user> --password-stdin
docker push <image>:<tag>
docker tag <image>:<old-tag> <image>:<new-tag>
docker logout

Next: Phase 6 — Production-Grade Practices (distributed agents, security, backups, monitoring). Ask me when you're ready.

Phase 6 of 7

Terraform, Ansible & AWS

Provision infrastructure and deploy your container end to end.

Goal of this phase: Learn just enough Terraform, Ansible, and AWS to wire real infrastructure provisioning and configuration into your Jenkins pipeline. This phase has two parts: (A) a focused primer on each tool standalone, then (B) integrating all three into your existing Jenkinsfile from Phase 5.

Assumes zero prior AWS/Terraform/Ansible knowledge, as requested. Builds on the jenkins-practice repo and Docker image pipeline from Phase 5.

Note on scope: This phase is intentionally denser than the others because three new tools are involved. Don't rush it — treat Part A almost like its own mini bootcamp before touching Jenkins again in Part B.


PART A: Standalone Primers

A1. AWS Basics (Primer)

What AWS actually is

AWS (Amazon Web Services) rents you computers, storage, and networking over the internet instead of you buying physical servers. You've already used one core AWS service without necessarily framing it this way: EC2 (Elastic Compute Cloud — virtual servers), which you used in Phase 1.

Core concepts you need for this phase

Concept Plain explanation
Region A physical geographic location of AWS data centers (e.g., us-east-1 = Northern Virginia). Pick one and stick with it while learning.
EC2 Instance A virtual server. Has a type (size, e.g., t2.micro = small/free-tier eligible), an OS image (AMI), and a public IP if configured.
AMI (Amazon Machine Image) A template/snapshot used to launch an EC2 instance (e.g., "Amazon Linux 2023" is an AMI).
Security Group A virtual firewall — rules for which ports/IPs can reach your instance (e.g., allow port 22 for SSH, port 8080 for Jenkins).
VPC (Virtual Private Cloud) Your own isolated network inside AWS. AWS gives you a "default VPC" automatically — fine to use while learning.
IAM (Identity and Access Management) Controls who (users, or programs like Jenkins) can do what in your AWS account.
IAM User / Access Key A set of credentials (Access Key ID + Secret Access Key) that lets a program (like Terraform, or Jenkins) authenticate to AWS.
S3 (Simple Storage Service) Object storage — think of it as a place to store files/backups (we'll use it for Terraform state later).

Hands-On: Create an IAM User for Programmatic Access

You should never use your AWS root account credentials for tools like Terraform. Create a dedicated IAM user instead.

  1. Log into the AWS Console → search IAM
  2. Users → Create user - Name: terraform-jenkins-user - Do NOT enable console access (this user is only for programmatic/API access)
  3. Attach policies directly → for learning purposes, attach AdministratorAccess (in a real company you'd scope this much tighter — but broad access simplifies learning right now)
  4. Create the user → go into it → Security credentials tab → Create access key - Use case: "Command Line Interface (CLI)" - Save the Access Key ID and Secret Access Key immediately — the secret is shown only once

Hands-On: Install & Configure AWS CLI

bash
# On Amazon Linux / RHEL-based systems
sudo yum install -y awscli

# Or via pip if not available in package manager
pip install awscli --user

# Configure with your new IAM user's keys
aws configure
# AWS Access Key ID: <paste it>
# AWS Secret Access Key: <paste it>
# Default region name: us-east-1   (or your chosen region)
# Default output format: json

Verify it works:

bash
aws sts get-caller-identity

This should print back your IAM user's account ID and ARN — confirming AWS trusts your credentials.


A2. Terraform Basics (Primer)

What Terraform actually is

Terraform is Infrastructure as Code (IaC) — instead of clicking around the AWS Console to create servers, you describe the infrastructure you want in a text file, and Terraform creates (and later, updates or destroys) it to match.

Core concepts

Concept Plain explanation
Provider A plugin that lets Terraform talk to a specific platform (AWS, Azure, GCP, etc.)
Resource A single piece of infrastructure you want (e.g., one EC2 instance, one S3 bucket)
.tf file Where you write your infrastructure definitions, in HashiCorp Configuration Language (HCL)
State file (terraform.tfstate) Terraform's record of what it has already created — critical, don't delete it manually
Plan A preview: "here's what I would create/change/destroy" — nothing happens yet
Apply Actually executes the plan — infrastructure gets created/changed
Destroy Tears down everything Terraform created — very useful for not leaving billable resources running while learning

Hands-On: Install Terraform

bash
# Amazon Linux / RHEL
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
sudo yum install -y terraform

# Verify
terraform -version

Hands-On: Your First Terraform Config

Create a new folder (separate from jenkins-practice for now):

bash
mkdir terraform-practice && cd terraform-practice

Create main.tf:

hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"   # match the region you configured in aws configure
}

resource "aws_instance" "practice_server" {
  ami           = "ami-0c101f26f147fa7fd"  # Amazon Linux 2023 (us-east-1) - verify current AMI ID in console if this errors
  instance_type = "t2.micro"

  tags = {
    Name = "terraform-practice-server"
  }
}

Important: AMI IDs are region-specific and change over time. Before running this, check the AWS Console → EC2 → AMI Catalog for the current Amazon Linux AMI ID in your region, and swap it in if needed.

Run the Terraform workflow:

bash
terraform init      # downloads the AWS provider plugin
terraform plan      # shows what will be created - review this carefully
terraform apply     # type 'yes' when prompted - actually creates the EC2 instance

Go check the AWS Console → EC2 → Instances — you should see terraform-practice-server running, created entirely from code.

When done experimenting, always clean up to avoid charges:

bash
terraform destroy   # type 'yes' when prompted

Key habit to build

Always run terraform plan before terraform apply and actually read the output. It tells you exactly what will change — this review step is what prevents accidental infrastructure damage in real jobs.


A3. Ansible Basics (Primer)

What Ansible actually is

Terraform creates infrastructure (the server itself). Ansible configures what's running inside it — installs packages, copies config files, starts services. Think: Terraform = "build the house," Ansible = "furnish and set up utilities inside it."

Core concepts

Concept Plain explanation
Control node The machine running Ansible commands (often your laptop or a CI server)
Managed node The remote server being configured (connects via SSH, no agent needed on the target)
Inventory A file listing which servers Ansible should manage
Playbook A YAML file describing the desired configuration steps ("tasks")
Module A built-in unit of work (e.g., yum, copy, service, template)
Task One step in a playbook, using a module
Idempotency Running the same playbook twice produces the same end state — it won't "double install" things unnecessarily

Hands-On: Install Ansible

bash
# Amazon Linux / RHEL
sudo yum install -y ansible

# Verify
ansible --version

Hands-On: Inventory File

Create inventory.ini (point this at the EC2 instance you created with Terraform, or any test EC2 instance you have SSH access to):

ini
[webservers]
practice_server ansible_host=<EC2_PUBLIC_IP> ansible_user=ec2-user ansible_ssh_private_key_file=~/path/to/your-key.pem

Test connectivity:

bash
ansible webservers -i inventory.ini -m ping

A successful response looks like "ping": "pong" in green — confirms Ansible can reach and authenticate to the server.

Hands-On: Your First Playbook

Create setup.yml:

yaml
---
- name: Configure web server
  hosts: webservers
  become: true            # run tasks with sudo

  tasks:
    - name: Install nginx
      yum:
        name: nginx
        state: present

    - name: Start and enable nginx
      service:
        name: nginx
        state: started
        enabled: true

    - name: Deploy a simple index page
      copy:
        content: "<h1>Configured by Ansible!</h1>"
        dest: /usr/share/nginx/html/index.html

Run it:

bash
ansible-playbook -i inventory.ini setup.yml

Then open http://<EC2_PUBLIC_IP> in a browser (make sure port 80 is open in the instance's Security Group) — you should see "Configured by Ansible!"

Run the playbook a second time and notice the output shows tasks as "ok" (unchanged) rather than "changed" where nothing needed to change — that's idempotency in action.


PART B: Integrating Terraform, Ansible & Docker into Jenkins

Now the payoff: wire all of this into the Jenkinsfile you built in Phase 5, so a single pipeline run can provision infrastructure, configure it, and deploy your Docker image to it.

B1. Store AWS Credentials in Jenkins

Same credentials pattern as GitHub (Phase 3) and Docker Hub (Phase 5):

  1. Manage Jenkins → Credentials → Global → Add Credentials - Kind: Secret text (add two separate entries) - Secret: your AWS Access Key ID → ID: aws-access-key-id - Secret: your AWS Secret Access Key → ID: aws-secret-access-key

B2. Install Terraform & Ansible on the Jenkins Agent

Same machine/container that runs your pipeline needs both tools available (same idea as installing Docker CLI in Phase 5).

bash
# If Jenkins runs natively on EC2, install directly on that instance:
sudo yum install -y ansible
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
sudo yum install -y terraform

# If Jenkins runs in Docker, exec into the container and install (apt-based image):
docker exec -u root jenkins bash -c "apt-get update && apt-get install -y ansible unzip curl"
docker exec -u root jenkins bash -c "curl -O https://releases.hashicorp.com/terraform/1.9.0/terraform_1.9.0_linux_amd64.zip && unzip terraform_1.9.0_linux_amd64.zip -d /usr/local/bin/"

B3. Add Terraform + Ansible Files to Your Repo

In your jenkins-practice repo, add a terraform/ folder with the main.tf from Part A2 (adjust the EC2 to also open port 80 for the app, and consider outputting the public IP — see below), and an ansible/ folder with inventory.ini and setup.yml adapted to deploy your Docker container instead of nginx directly.

Add an output to your terraform/main.tf so the pipeline can capture the new server's IP automatically:

hcl
output "instance_public_ip" {
  value = aws_instance.practice_server.public_ip
}

B4. Extend the Jenkinsfile

Add these stages to your existing Jenkinsfile (after the Docker Push stage from Phase 5):

groovy
environment {
    // ...existing DOCKERHUB vars from Phase 5...
    AWS_ACCESS_KEY_ID     = credentials('aws-access-key-id')
    AWS_SECRET_ACCESS_KEY = credentials('aws-secret-access-key')
    AWS_DEFAULT_REGION    = 'us-east-1'
}

stage('Terraform Init & Apply') {
    steps {
        dir('terraform') {
            sh 'terraform init'
            sh 'terraform plan -out=tfplan'
            input message: 'Apply this Terraform plan?', ok: 'Apply'  // manual gate, from Phase 4
            sh 'terraform apply -auto-approve tfplan'
        }
    }
}

stage('Capture Server IP') {
    steps {
        dir('terraform') {
            script {
                env.SERVER_IP = sh(
                    script: 'terraform output -raw instance_public_ip',
                    returnStdout: true
                ).trim()
            }
            echo "Provisioned server IP: ${env.SERVER_IP}"
        }
    }
}

stage('Ansible Configure & Deploy') {
    steps {
        dir('ansible') {
            sh """
                echo "[webservers]" > inventory.ini
                echo "server ansible_host=${env.SERVER_IP} ansible_user=ec2-user" >> inventory.ini
                ansible-playbook -i inventory.ini setup.yml \
                  --extra-vars "docker_image=${IMAGE_NAME}:${IMAGE_TAG}" \
                  --ssh-common-args='-o StrictHostKeyChecking=no'
            """
        }
    }
}

What this pipeline now does, end to end: 1. Builds your app's Docker image (Phase 5) 2. Pushes it to Docker Hub (Phase 5) 3. Uses Terraform to provision a fresh EC2 instance (or update an existing one) — with a manual approval gate before anything is actually created, since infra changes cost money and carry risk 4. Captures the new server's IP automatically from Terraform's output 5. Hands that IP to Ansible, which connects via SSH and configures the server — pulling and running your Docker image

This is the full loop described back in the original 6-phase plan: GitHub push → Jenkins → Docker build/push → Terraform provisions → Ansible configures/deploys.

Important reminder: run terraform destroy manually (or add a separate "teardown" pipeline job) when you're done practicing, so you don't leave EC2 instances running and racking up charges.


Checkpoint — Can You Answer These?

  1. In your own words, what's the difference in responsibility between Terraform and Ansible?
  2. Why create a dedicated IAM user for Terraform/Jenkins instead of using your AWS root account?
  3. Why should you always run terraform plan before terraform apply?
  4. What does "idempotency" mean, and why does it matter for Ansible playbooks?
  5. In the Jenkinsfile, how does the pipeline get the new server's IP address without you typing it in manually?
  6. Why is there a manual input gate before terraform apply in the pipeline?

If you can answer all six, you've now covered the entire original 6-tool integration loop. From here, Phase 7: Production-Grade Practices (distributed agents, RBAC/security, backups, monitoring) is the natural next and final step in the core curriculum.


Quick Reference — This Phase

bash
# AWS
aws configure
aws sts get-caller-identity

# Terraform
terraform init
terraform plan
terraform apply
terraform destroy
terraform output -raw <output_name>

# Ansible
ansible <group> -i inventory.ini -m ping
ansible-playbook -i inventory.ini playbook.yml

Next: Phase 7 — Production-Grade Practices (distributed agents, security, backups, monitoring). Ask me when you're ready.

Phase 7 of 7

Production Practices

Distributed agents, access control, backups, monitoring.

Goal of this phase: Take everything you've built (Phases 1–6) and learn the practices that separate a "learning lab" Jenkins from one a company could actually trust in production — distributed agents, access control, backups, and monitoring.

This is the final phase of the core curriculum. Builds on everything before it — your jenkins-practice repo, Docker/Terraform/Ansible pipeline, and credentials store.


1. Distributed Builds: Controller + Multiple Agents (Concept)

So far, everything has run on the built-in node (the controller doing double duty as an agent). In production, this doesn't scale — you want the controller focused on orchestration/UI, while separate agent machines actually run builds. Benefits: - Parallel capacity — many builds run at once across different machines - Isolation — one heavy/broken build doesn't slow down or crash Jenkins itself - Specialization — some agents can have Docker + AWS CLI, others might have Windows-only tooling, etc.

Ways to connect an agent

Method How it works
SSH Controller connects to the agent machine over SSH and launches an agent process — most common, simplest to set up
JNLP (inbound agent) The agent machine initiates the connection to the controller (useful when the agent is behind a firewall/NAT)
Docker agent / Kubernetes agent Agents are spun up as containers/pods on demand, then destroyed after the build — very common in modern setups, scales elastically

Hands-On: Add an SSH Agent

  1. Launch a second EC2 instance (separate from your Jenkins controller) — this will be your dedicated agent
  2. Install Java on it (agents need Java too): bash sudo yum install -y java-17-amazon-corretto
  3. On the Jenkins controller: Manage Jenkins → Credentials — add the agent's SSH private key as a "SSH Username with private key" credential (same pattern you've used before)
  4. Manage Jenkins → Nodes → New Node - Name: agent-1 - Type: Permanent Agent
  5. Configure: - Remote root directory: /home/ec2-user/jenkins-agent (any writable path) - Labels: linux docker (tags you can target in pipelines) - Launch method: Launch agents via SSH
    • Host: agent's public IP
    • Credentials: the SSH key you just added
    • Host Key Verification Strategy: "Non verifying" (fine for learning; stricter in real prod)
  6. Save — Jenkins will connect and show the agent as online (green) in Manage Jenkins → Nodes

Hands-On: Target a Specific Agent in a Pipeline

In your Jenkinsfile, instead of agent any, target the label:

groovy
pipeline {
    agent { label 'linux && docker' }
    // ...rest of pipeline
}

Or target specific stages to specific agents:

groovy
stage('Build') {
    agent { label 'docker' }
    steps {
        sh 'docker build -t myapp .'
    }
}

Run the pipeline and confirm (in Console Output, near the top) it says it's running on agent-1 rather than the built-in node.


2. Security: Users, Roles, and Access Control (Concept)

By default, your Jenkins likely has one admin account (from initial setup). In a real team, you need multiple users with different permission levels — not everyone should be able to delete jobs or view credentials.

Key concepts

Term Meaning
Authentication Who are you? (login)
Authorization What are you allowed to do? (permissions)
Matrix-based security Built-in Jenkins authorization strategy — grid of users/groups × permissions (Read, Build, Configure, Delete, etc.)
Role-Based Strategy (plugin) More scalable — define roles (e.g., "Developer," "Viewer," "Admin") once, then assign users to roles instead of managing permissions per-user

Hands-On: Enable Matrix-Based Security

  1. Install plugin if not present: Manage Jenkins → Plugins → Available → "Role-based Authorization Strategy"
  2. Manage Jenkins → Security → Authorization → select Role-Based Strategy
  3. Manage Jenkins → Manage and Assign Roles → Manage Roles - Create a Global role called developer with permissions: Overall Read, Job Build, Job Read, Job Workspace - Create a Global role called viewer with only: Overall Read, Job Read
  4. Manage and Assign Roles → Assign Roles — create a second test user (Manage Jenkins → Users → Create User) and assign them the viewer role
  5. Log out, log in as the test user — confirm they can see jobs but cannot click "Build Now" or "Configure"

This is exactly the pattern real companies use: junior engineers get viewer or developer, senior/DevOps engineers get broader roles, and only a small admin group gets full control.

A Note on Credentials Visibility

Credentials you stored in earlier phases (GitHub token, Docker Hub, AWS keys) are never shown in plain text to any user, regardless of role — Jenkins masks them by design. Roles control who can use/reference a credential in a job, not who can view the raw secret.


3. Backup & Disaster Recovery (Concept)

Everything Jenkins knows — job configs, credentials, build history, plugins — lives under one directory: JENKINS_HOME. - Native install: /var/lib/jenkins - Docker install: inside the jenkins_home volume you mounted back in Phase 1

If this is lost and not backed up, you lose everything — every job, every pipeline, every credential.

Hands-On: Manual Backup (Native Install)

bash
# Stop Jenkins first for a clean, consistent backup (avoids mid-write corruption)
sudo systemctl stop jenkins

# Create a compressed backup
sudo tar -czf jenkins-backup-$(date +%Y%m%d).tar.gz -C /var/lib jenkins

# Restart Jenkins
sudo systemctl start jenkins

# Optional: move the backup somewhere durable, e.g. an S3 bucket (you learned S3 basics in Phase 6)
aws s3 cp jenkins-backup-$(date +%Y%m%d).tar.gz s3://your-backup-bucket/jenkins/

Hands-On: Manual Backup (Docker Install)

bash
docker stop jenkins

docker run --rm \
  -v jenkins_home:/var/jenkins_home \
  -v $(pwd):/backup \
  busybox tar -czf /backup/jenkins-backup-$(date +%Y%m%d).tar.gz -C /var/jenkins_home .

docker start jenkins

Restore (either setup)

Stop Jenkins, extract the backup archive back into JENKINS_HOME (or the mounted volume), then restart. Test this at least once — a backup you've never restored from is not a verified backup.

Better long-term approach (awareness only for now)

In real production, this is usually automated: a scheduled cron job or a Jenkins job itself (a nice "meta" use of Jenkins — using it to back up itself) running nightly, pushing to S3 or similar durable storage with retention rules.


4. Monitoring Jenkins Health (Concept)

You want to know before Jenkins runs out of disk space or memory, not after builds start silently failing.

Built-in options

  • Manage Jenkins → System Information — Java version, memory usage snapshot, environment variables
  • Manage Jenkins → System Log — real-time Jenkins internal logs, useful for debugging plugin or connectivity issues

Hands-On: Install the Monitoring Plugin

  1. Manage Jenkins → Plugins → Available → search "Monitoring" → install
  2. After install, go to Manage Jenkins → Monitoring — you'll get graphs for JVM memory, HTTP request throughput, thread counts, etc.

Connecting to Your Existing AWS Knowledge

Since you now know AWS basics (Phase 6), the production-grade version of this is: install the CloudWatch agent on your Jenkins EC2 instance to ship system-level metrics (CPU, disk, memory) into AWS CloudWatch, then set CloudWatch Alarms (e.g., "alert if disk usage > 85%"). This is genuinely how many companies monitor self-hosted Jenkins — worth exploring once you're comfortable, but treat it as a stretch goal rather than a required exercise here.


5. Optional: Blue Ocean UI

Blue Ocean is a friendlier visual interface for Pipelines specifically (less useful for Freestyle jobs).

  1. Manage Jenkins → Plugins → Available → search "Blue Ocean" → install
  2. Click Open Blue Ocean in the left sidebar
  3. Browse your hello-world-pipeline or jenkins-practice pipeline — notice the cleaner stage visualization, inline log viewing per-stage, and a more modern layout

Purely optional — functionally it doesn't change what Jenkins does, just how it's presented. Some teams love it, some stick with the classic UI. Worth knowing it exists.


6. Checkpoint — Can You Answer These?

  1. Why would a company use multiple agents instead of running all builds on the controller?
  2. What's the difference between authentication and authorization?
  3. Why can't even an admin-role user see a stored credential's raw value in Jenkins?
  4. What directory holds everything Jenkins needs to be restored, and why should you stop Jenkins before backing it up?
  5. Why is "a backup you've never restored from" considered unreliable?
  6. Name two things you'd want to monitor on a Jenkins server and why each matters.

If you can answer all six, you've completed the full 7-phase Jenkins curriculum — from zero, to a working end-to-end pipeline (GitHub → Jenkins → Docker → Terraform → Ansible), to the production practices real teams rely on.


Quick Reference — This Phase

bash
# SSH agent prerequisite
sudo yum install -y java-17-amazon-corretto

# Backup (native)
sudo systemctl stop jenkins
sudo tar -czf jenkins-backup-$(date +%Y%m%d).tar.gz -C /var/lib jenkins
sudo systemctl start jenkins

# Backup (Docker)
docker stop jenkins
docker run --rm -v jenkins_home:/var/jenkins_home -v $(pwd):/backup busybox \
  tar -czf /backup/jenkins-backup-$(date +%Y%m%d).tar.gz -C /var/jenkins_home .
docker start jenkins
groovy
// Targeting a labeled agent in a pipeline
pipeline {
    agent { label 'linux && docker' }
    ...
}

What's Next (Beyond This Curriculum)

You now have the full core skillset. A few natural directions from here, whenever you're ready — just ask and I'll build a guide the same way:

  • Shared Libraries — reusable Groovy pipeline code across multiple Jenkinsfiles/projects (useful once you have several repos following the same pattern)
  • Kubernetes agents — dynamic, on-demand agents instead of static EC2 agents
  • Multibranch Pipelines — automatically create a pipeline per Git branch/PR, common in team workflows
  • Real project: put the full loop (GitHub → Jenkins → Docker → Terraform → Ansible) on your resume as a portfolio project, since you've now genuinely built it end-to-end

Congratulations on finishing the curriculum — this is a strong, resume-ready skill set for a Cloud & DevOps Engineer role.

You've reached the end