Jenkins Lesson 18 – Credentials Management | Dataplexa
Section II · Lesson 18

Credentials Management

Secrets hardcoded in pipelines get committed to Git. Git commits live forever. This lesson is about doing it right — storing credentials securely in Jenkins and injecting them into pipelines without ever exposing them in a log or a repository.

This lesson covers

The Jenkins credential store → Credential types → Storing credentials in the UI → Injecting credentials into pipelines → Scopes → The most common mistakes teams make

Every team that hasn't set this up properly has the same story. A developer needed to connect Jenkins to a Docker registry. In a hurry, they typed the password directly into the Jenkinsfile. It got committed. Three years later, that password is in a public GitHub repo and someone's using the registry for free crypto mining. It takes five minutes to do this correctly. Those five minutes are worth it.

The Analogy

The Jenkins credential store is a safe inside the building. Your pipeline doesn't carry the key — it tells the safe "I need item 42" and the safe hands it over, masked, just for that build. The key never leaves the safe. The value never appears in any log. Anyone who can read the pipeline code only sees the ID — not the secret.

The Jenkins Credential Store

Jenkins stores all credentials in an encrypted store. The credentials are encrypted at rest on the Jenkins master using a key that lives in JENKINS_HOME/secrets/. Even if someone gets access to the raw files on disk, the credentials are not readable without the encryption key.

To add a credential, go to Manage Jenkins → Credentials → System → Global credentials (unrestricted) → Add Credentials. Here's what that screen looks like:

Jenkins — Add Credentials
Username with password ▾
Global (Jenkins, nodes, items, all child items, etc) ▾
deployer
••••••••••••••••
docker-registry-credentials
Docker registry login for acmecorp.registry.io
Save

The Credential Types You'll Use Most

Username with password

usernamePassword()

Stores a username and password pair. Jenkins creates two environment variables: CREDS_USR (the username) and CREDS_PSW (the password). Used for Docker registries, Maven repositories, and any service requiring a login.

Secret text

string()

A single masked string — API keys, Slack webhook URLs, auth tokens. Creates one environment variable with the value masked in all logs. The most common type for modern API-based integrations.

SSH username with private key

sshUserPrivateKey()

Stores an SSH private key. Jenkins writes it to a temporary file on the agent during the build and injects the file path as an environment variable. Used for SSH-based Git authentication and server deployments.

Secret file

file()

Stores a file — a kubeconfig, a service account JSON key, a .pem certificate. Jenkins writes it to a temporary location during the build and gives you the path. Deleted when the build finishes.

Certificate (PKCS#12)

certificate()

Stores a PKCS#12 certificate with its private key. Used for mutual TLS authentication and code signing. Less common but essential in regulated industries.

Scopes — Who Can See What

When you create a credential, you choose a scope. Scope controls which jobs and systems can use it. Getting scope right is part of the principle of least privilege — a credential should only be accessible to the jobs that genuinely need it.

Global

Available everywhere — all pipelines, all nodes, all child items. Use for shared credentials like a Docker registry that every team's pipeline needs.

System

Only available to Jenkins itself — for connecting to agents via SSH, sending email, or other internal operations. Not accessible inside pipeline steps.

Folder-scoped

Available only to jobs inside a specific folder. Use for team-specific credentials — the payments team's deploy key should only be accessible to payments team jobs.

Three Ways to Inject Credentials Into a Pipeline

The scenario:

You're a DevOps engineer building a deployment pipeline for the analytics-service. The pipeline needs three different secrets: a Docker registry login, a Slack webhook URL for notifications, and an SSH key to deploy to the production server. Each is stored in the Jenkins credential store. Here are the three ways to inject them, depending on the use case.

New terms in this code:

  • environment { credentials('id') } — injects a credential as a pipeline-wide environment variable. Available to all stages. Jenkins automatically creates suffixed variables (_USR, _PSW) for username/password types.
  • withCredentials([ ]) — a step that injects credentials only for the duration of the block. More granular than environment injection — use when you only need the secret in one place. The credential is unavailable outside the block.
  • usernamePassword() — the binding type inside withCredentials for username/password credentials. You specify which variable names to use for username and password.
  • string(credentialsId:, variable:) — the binding type for secret text credentials. Injects the secret text as a single named variable.
  • sshUserPrivateKey() — the binding type for SSH credentials. Jenkins writes the private key to a temp file and injects the file path as a variable.
pipeline {
    agent { label 'linux' }

    // Method 1: environment block injection
    // Credentials are available to ALL stages as environment variables
    // Best for credentials used in multiple stages
    environment {
        // For a username/password credential, Jenkins creates two variables:
        // DOCKER_CREDS_USR = the username
        // DOCKER_CREDS_PSW = the password (masked in logs)
        DOCKER_CREDS  = credentials('docker-registry-credentials')

        // For a secret text credential, creates one variable with the secret value
        SLACK_WEBHOOK = credentials('slack-webhook-url')
    }

    stages {

        stage('Build and Push Image') {
            steps {
                // Use the variables injected by the environment block
                // DOCKER_CREDS_USR and DOCKER_CREDS_PSW are available here
                sh """
                    docker login \
                      -u ${DOCKER_CREDS_USR} \
                      -p ${DOCKER_CREDS_PSW} \
                      registry.acmecorp.com

                    docker build -t registry.acmecorp.com/analytics-service:${BUILD_NUMBER} .
                    docker push registry.acmecorp.com/analytics-service:${BUILD_NUMBER}
                """
            }
        }

        stage('Deploy to Production') {
            when { branch 'main' }
            steps {
                // Method 2: withCredentials block
                // The SSH key is only available inside this block
                // Best for credentials needed in just one place
                withCredentials([
                    sshUserPrivateKey(
                        credentialsId: 'prod-server-ssh-key',
                        keyFileVariable: 'SSH_KEY_FILE',   // path to the temp key file
                        usernameVariable: 'SSH_USER'        // the SSH username
                    )
                ]) {
                    // SSH_KEY_FILE contains the path to the private key file Jenkins created
                    sh """
                        ssh -i ${SSH_KEY_FILE} \
                            -o StrictHostKeyChecking=no \
                            ${SSH_USER}@prod-server-01.acmecorp.com \
                            'docker pull registry.acmecorp.com/analytics-service:${BUILD_NUMBER} && \
                             docker-compose up -d'
                    """
                }
                // SSH_KEY_FILE and SSH_USER are no longer available here
                // Jenkins deletes the temporary key file after the block closes
            }
        }

        stage('Notify') {
            steps {
                // Method 3: withCredentials for a secret text credential
                // Useful when you need fine-grained control over when the secret is used
                withCredentials([
                    string(
                        credentialsId: 'slack-webhook-url',
                        variable: 'SLACK_URL'
                    )
                ]) {
                    sh """
                        curl -X POST \
                          -H 'Content-type: application/json' \
                          --data '{"text":"analytics-service ${BUILD_NUMBER} deployed to production"}' \
                          ${SLACK_URL}
                    """
                }
            }
        }

    }

    post {
        failure {
            // SLACK_WEBHOOK is available here — it was injected at pipeline level
            sh """
                curl -X POST \
                  -H 'Content-type: application/json' \
                  --data '{"text":"analytics-service pipeline FAILED — build ${BUILD_NUMBER}"}' \
                  ${SLACK_WEBHOOK}
            """
        }
        always { cleanWs() }
    }

}

Where to practice: In your Jenkins instance, go to Manage Jenkins → Credentials → System → Global credentials → Add Credentials. Add a "Secret text" credential with ID slack-webhook-url and paste any test value. Then add the Jenkinsfile above and observe how Jenkins masks the value in the console output. Full credentials binding reference at jenkins.io — Credentials Binding Plugin.

Started by SCM change
[Pipeline] Start of Pipeline
[Pipeline] node (agent-linux-01)
[Pipeline] withCredentials
Masking supported pattern matches of $DOCKER_CREDS_PSW or $DOCKER_CREDS_USR
Masking supported pattern matches of $SLACK_WEBHOOK
[Pipeline] { (Build and Push Image) }
[Pipeline] sh
+ docker login -u deployer -p **** registry.acmecorp.com
Login Succeeded
+ docker build -t registry.acmecorp.com/analytics-service:91 .
Successfully built 4f2a1b3c
+ docker push registry.acmecorp.com/analytics-service:91
Pushed: registry.acmecorp.com/analytics-service:91
[Pipeline] { (Deploy to Production) }
[Pipeline] withCredentials
Masking supported pattern matches of $SSH_KEY_FILE or $SSH_USER
[Pipeline] sh
+ ssh -i /tmp/ssh-key-14729483 -o StrictHostKeyChecking=no deployer@prod-server-01.acmecorp.com ...
Pulling analytics-service:91...
Container restarted. Status: Up 3 seconds
[Pipeline] { (Notify) }
[Pipeline] withCredentials
Masking supported pattern matches of $SLACK_URL
[Pipeline] sh
+ curl -X POST -H 'Content-type: application/json' --data ... ****
ok
[Pipeline] End of Pipeline
Finished: SUCCESS

What just happened?

  • Masking supported pattern matches of $DOCKER_CREDS_PSW — Jenkins announces at the start of the build which variables it will mask. Every time the value of DOCKER_CREDS_PSW appears anywhere in the console output, Jenkins replaces it with ****. This is automatic — you don't need to do anything extra.
  • -p **** — the Docker password was passed to the command, but Jenkins masked it before printing. The actual password was used correctly — it just never appeared in any log.
  • /tmp/ssh-key-14729483 — Jenkins created a temporary file on the agent containing the SSH private key and injected its path as SSH_KEY_FILE. This file is created at the start of the withCredentials block and deleted when the block closes. The key never lives on disk any longer than necessary.
  • --data ... **** — the Slack webhook URL was masked in the curl command. Even though it was passed as a command argument, Jenkins caught it and replaced it with stars. This applies to all output — shell commands, echoes, error messages — not just specific locations you tell Jenkins about.
  • The Notify stage used withCredentials even though the same credential is in the environment block — this was intentional in the example to show both methods. In practice, if a credential is already in the environment block, you can just use the variable directly in any stage without a second withCredentials block.

The Four Mistakes That Leak Credentials

Printing credentials with echo

Even though Jenkins masks credentials in shell output, it does not mask them if you explicitly echo them with echo "My password is ${MY_CRED}" in a Groovy context. Never echo credential variables directly. Jenkins masks the value in sh output, but Groovy string interpolation in echo steps bypasses the masking in some versions.

Hardcoding secrets in the Jenkinsfile

The Jenkinsfile is committed to Git. Anything hardcoded in it is in your Git history permanently — even if you remove it later. Every secret must come from the credential store, never from the Jenkinsfile itself.

Using Global scope for everything

A production deploy key with Global scope is accessible to every job on Jenkins — including jobs run by contractors, interns, or compromised pipeline scripts. Use folder-scoped credentials for anything sensitive and limit Global scope to truly shared infrastructure credentials.

Storing credentials without a description

Six months from now, nobody will know what cred-42 is or who owns it. Every credential must have a clear ID, description, and owner. A credential store with 80 mystery entries is as dangerous as no credential store — someone will delete the wrong one.

Teacher's Note

Audit your credential store every quarter. Delete anything unused. Rotate anything that hasn't been rotated in a year. Treat it like a production database — with care and a maintenance schedule.

Practice Questions

1. Which pipeline step injects a Jenkins credential only for the duration of its block, making it unavailable outside?



2. If you inject a username/password credential with ID docker-registry-credentials using DOCKER_CREDS = credentials('docker-registry-credentials'), what is the name of the variable that holds the username?



3. Which withCredentials binding type writes an SSH private key to a temporary file on the agent and injects the file path as a variable?



Quiz

1. How does the Jenkins credential store protect secrets?


2. A payments team has an SSH key that should only be usable by their own pipeline jobs. Which credential scope should you use?


3. A developer wants to use an API key in their pipeline. What is the correct approach?


Up Next · Lesson 19

Pipeline Triggers

SCM polling, webhooks, schedules, and upstream triggers — how to make your pipeline fire automatically at exactly the right moment.