Jenkins Setup and Configuration#

Jenkins is a self-hosted automation server. Unlike managed CI services, you own the infrastructure, which means you control everything from plugin versions to executor capacity. This guide covers the three main installation methods and the configuration patterns that make Jenkins manageable at scale.

Installation with Docker#

The fastest way to run Jenkins locally or in a VM:

docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts-jdk17

Port 8080 is the web UI. Port 50000 is the JNLP agent port for inbound agent connections. The volume mount is critical – without it, all configuration and build history is lost when the container restarts.

Retrieve the initial admin password:

docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Installation with Helm on Kubernetes#

The jenkins/jenkins Helm chart is the standard approach for running Jenkins on Kubernetes:

helm repo add jenkins https://charts.jenkins.io
helm repo update

helm install jenkins jenkins/jenkins \
  --namespace jenkins \
  --create-namespace \
  -f jenkins-values.yaml

A practical jenkins-values.yaml:

controller:
  image:
    tag: "lts-jdk17"
  resources:
    requests:
      cpu: "500m"
      memory: "1Gi"
    limits:
      cpu: "2"
      memory: "3Gi"
  serviceType: ClusterIP
  ingress:
    enabled: true
    hostName: jenkins.example.com
  installPlugins:
    - kubernetes:latest
    - workflow-aggregator:latest
    - git:latest
    - configuration-as-code:latest
    - credentials-binding:latest
  JCasC:
    configScripts:
      welcome-message: |
        jenkins:
          systemMessage: "Jenkins configured via Helm + JCasC"
persistence:
  enabled: true
  size: 20Gi

The installPlugins list runs during startup, so the controller pod comes up with everything it needs. The JCasC.configScripts block injects Configuration as Code YAML directly into the pod.

Get the admin password after install:

kubectl exec -n jenkins svc/jenkins -c jenkins -- cat /run/secrets/additional/chart-admin-password

Installation with Package Managers#

On Debian/Ubuntu:

curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/" | sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt update && sudo apt install jenkins
sudo systemctl enable --now jenkins

Jenkins runs on port 8080 by default. The initial password is at /var/lib/jenkins/secrets/initialAdminPassword.

Jenkins Configuration as Code (JCasC)#

JCasC eliminates manual UI configuration. You define Jenkins state in YAML files, and the configuration-as-code plugin applies them on startup or reload. Place YAML files in $JENKINS_HOME/casc_configs/ or set the CASC_JENKINS_CONFIG environment variable.

A JCasC file covering common settings:

jenkins:
  systemMessage: "Production Jenkins - managed by JCasC"
  numExecutors: 0  # no builds on controller
  securityRealm:
    local:
      allowsSignup: false
      users:
        - id: "admin"
          password: "${JENKINS_ADMIN_PASSWORD}"
  authorizationStrategy:
    roleBased:
      roles:
        global:
          - name: "admin"
            permissions:
              - "Overall/Administer"
            entries:
              - user: "admin"
          - name: "readonly"
            permissions:
              - "Overall/Read"
              - "Job/Read"
            entries:
              - user: "authenticated"
  nodes:
    - permanent:
        name: "build-agent-1"
        remoteFS: "/home/jenkins/agent"
        launcher:
          ssh:
            host: "agent1.internal"
            credentialsId: "agent-ssh-key"
            sshHostKeyVerificationStrategy: "knownHostsFileKeyVerificationStrategy"
        numExecutors: 4
        labels: "linux docker"

credentials:
  system:
    domainCredentials:
      - credentials:
          - usernamePassword:
              scope: GLOBAL
              id: "github-creds"
              username: "deploy-bot"
              password: "${GITHUB_TOKEN}"
          - basicSSHUserPrivateKey:
              scope: GLOBAL
              id: "agent-ssh-key"
              username: "jenkins"
              privateKeySource:
                directEntry:
                  privateKey: "${SSH_PRIVATE_KEY}"
          - string:
              scope: GLOBAL
              id: "slack-webhook"
              secret: "${SLACK_WEBHOOK_URL}"

Variable interpolation (${VAR}) pulls from environment variables, which keeps secrets out of the YAML. In Kubernetes, inject them from Kubernetes Secrets via the Helm chart’s controller.containerEnv or controller.additionalExistingSecrets.

To reload JCasC without restarting Jenkins, go to Manage Jenkins > Configuration as Code > Apply new configuration, or hit the API:

curl -X POST "http://jenkins:8080/configuration-as-code/apply" \
  --user admin:$API_TOKEN

Managing Plugins#

Plugins are Jenkins’s strength and its biggest maintenance burden. Key principles:

  • Pin versions in production. Using latest in dev is fine; in production, pin: kubernetes:4234.vdc5660543d1a.
  • Use the Jenkins CLI or API for bulk operations:
# List installed plugins
curl -s "http://jenkins:8080/pluginManager/api/json?depth=1" \
  --user admin:$API_TOKEN | jq '.plugins[] | {shortName, version}'

# Install a plugin via CLI
java -jar jenkins-cli.jar -s http://jenkins:8080/ install-plugin pipeline-stage-view -restart
  • The installPlugins list in the Helm chart runs the jenkins-plugin-cli tool at startup. Plugins are downloaded before Jenkins boots, so the instance is ready immediately.

Credentials Management#

Jenkins credentials are scoped to domains and accessed by ID in pipelines. The three most common types:

  1. Username with password – for Git HTTPS, Docker registries, API tokens.
  2. SSH username with private key – for Git SSH, remote agents.
  3. Secret text – for webhook URLs, API keys, single-value secrets.

In a Jenkinsfile, bind credentials to environment variables:

environment {
    DOCKER_CREDS = credentials('docker-registry')   // sets DOCKER_CREDS_USR and DOCKER_CREDS_PSW
    API_KEY = credentials('my-api-key')              // secret text, sets API_KEY directly
}

Configuring Agents#

Setting numExecutors: 0 on the controller is a security and stability best practice. All builds should run on agents. Agents connect to Jenkins via SSH (Jenkins connects out to the agent) or JNLP/WebSocket (agent connects in to Jenkins).

For static agents, the JCasC nodes block shown above is the declarative approach. For dynamic agents on Kubernetes, the kubernetes plugin creates pods on demand – see the Kubernetes integration article for details.

The key agent configuration parameters are remoteFS (the working directory on the agent), labels (used in pipeline agent { label 'linux' } directives), and numExecutors (how many concurrent builds the agent handles).