The Problem: Manual Jenkins Configuration is a Nightmare

If you’ve managed Jenkins for any length of time, you know the pain. A new server crashes, and you need to:

  • Recreate users and permissions
  • Reinstall plugins
  • Reconfigure security settings
  • Rebuild all your job definitions
  • Hope you remember which obscure checkbox you clicked last time

Then you get to explain to your team why everything is down because nobody documented how the “old” Jenkins was set up.

There’s a better way.

What is Jenkins Configuration as Code (JCasC)?

Jenkins Configuration as Code (often shortened to JCasC) lets you define your entire Jenkins setup in YAML configuration files instead of clicking through the web UI. Everything—users, security, plugins, jobs, tools, agents—becomes code that lives in version control alongside your actual project code.

Think of it like Infrastructure as Code (IaC) for Jenkins. Just as you version-control your application code, you now version-control your Jenkins configuration.

Why Should You Care?

Speed: New Jenkins instance? Start it with full configuration in minutes.

Reliability: Disaster recovery becomes straightforward—redeploy the YAML, and you’re back online with identical configuration.

Collaboration: Your team knows exactly how Jenkins is configured because it’s in Git. No more “how did this get set up?” questions.

Docker & Kubernetes: Containerization becomes natural. Your Jenkins config travels with your container.

Auditability: Every configuration change is tracked in version control with commit messages and blame history.

Benefits of JCasC

  • Reproducible environments - spin up identical Jenkins servers consistently
  • Disaster recovery - rebuild your Jenkins setup from version control
  • Version control - track configuration changes like code
  • Less UI work - declare what you want, not what to click
  • Simplified deployment - Docker and Kubernetes work naturally
  • Team collaboration - everyone sees and understands the setup
  • Automation-friendly - no manual steps needed

Complete Working Example: Jenkins with Docker

Here’s a complete, production-ready example that works out of the box. I’ve tested this setup, and it will bring up Jenkins with automatic configuration in under 2 minutes.

Project Structure

jenkins-casc-demo/
├── docker-compose.yml
├── Dockerfile
├── plugins.txt
└── jenkins.yaml

Step 1: Dockerfile

FROM jenkins/jenkins:lts-jdk21

# Copy plugin list to the Jenkins reference directory
COPY plugins.txt /usr/share/jenkins/ref/plugins.txt

# Install all plugins specified in plugins.txt
RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt

What this does:

  • Starts from official Jenkins LTS image with Java 21
  • Copies your plugin list
  • Pre-installs all plugins so they’re ready when Jenkins starts

Step 2: plugins.txt

configuration-as-code
job-dsl
pipeline-model-definition
git

Explanation:

  • configuration-as-code - enables JCasC functionality
  • job-dsl - allows programmatic job creation (optional but recommended)
  • pipeline-model-definition - Declarative Pipelines support
  • git - Git integration for pulling code

Add or remove plugins as needed for your use case.

Step 3: docker-compose.yml

services:
  jenkins:
    build: .
    container_name: jenkins-casc-demo

    ports:
      - "8080:8080"
      - "50000:50000"

    environment:
      - CASC_JENKINS_CONFIG=/var/jenkins_home/casc_configs/jenkins.yaml

    volumes:
      - ./jenkins.yaml:/var/jenkins_home/casc_configs/jenkins.yaml
      - jenkins_home:/var/jenkins_home

volumes:
  jenkins_home:

Key configuration:

  • CASC_JENKINS_CONFIG - tells Jenkins where to find your configuration file
  • Port 8080 - Jenkins web UI
  • Port 50000 - agent communication
  • Volume mount for jenkins.yaml - the configuration file
  • Named volume jenkins_home - persistent storage across restarts

Step 4: jenkins.yaml (The Core Configuration)

jenkins:
  systemMessage: "Jenkins configured by Configuration as Code"

  # System settings
  numExecutors: 2
  mode: NORMAL

  # Security configuration
  securityRealm:
    local:
      allowsSignup: false

  # Authorization
  authorizationStrategy:
    loggedInUsersCanDoAnything:
      allowAnonymousRead: false

  # Configure Jenkins URLs
  crumbIssuer:
    standard:
      excludeClientIPFromCrumb: false

# Helper to create credentials (username/password)
credentials:
  system:
    domainCredentials:
      - credentials:
          - basic:
              id: "demo-credentials"
              username: "demo-user"
              password: "${DEMO_PASSWORD}"
              description: "Demo credentials for illustration"

# Security-related settings
security:
  apiToken:
    creationOfLegacyTokenEnabled: false
  scriptApproval:
    # Pre-approve scripts to avoid sandbox prompts
    signatures: []

# Unclassified settings
unclassified:
  location:
    url: "http://localhost:8080/"

# Create a sample pipeline job using Job DSL
jobs:
  - script: >
      pipelineJob('hello-casc') {
        displayName('Sample Hello World Job')
        description('Auto-created by Configuration as Code')

        definition {
          cps {
            script('''
              pipeline {
                agent any

                stages {
                  stage('Hello') {
                    steps {
                      echo "=============================================="
                      echo "Hello from Jenkins Configuration as Code!"
                      echo "=============================================="
                      sh 'date'
                      sh 'whoami'
                    }
                  }

                  stage('Info') {
                    steps {
                      echo "Jenkins is running with JCasC enabled"
                      echo "Configuration is version-controlled in Git"
                    }
                  }
                }

                post {
                  success {
                    echo "✅ Pipeline completed successfully!"
                  }
                }
              }
            ''')
            sandbox(true)
          }
        }
      }      

What’s happening here:

  • numExecutors: 2 - allow 2 concurrent builds
  • securityRealm.local - use Jenkins built-in user database
  • allowsSignup: false - don’t allow registration
  • loggedInUsersCanDoAnything - all logged-in users have full permissions (appropriate for dev/demo only)
  • credentials - define credentials that jobs can use (passwords via environment variables is more secure)
  • jobs - the Job DSL section creates jobs programmatically

How to Run It

1. Create a directory structure:

mkdir jenkins-casc-demo
cd jenkins-casc-demo

2. Create the four files shown above in this directory

3. Run Jenkins:

docker compose up --build

You’ll see output like:

jenkins-casc-demo  | ...
jenkins-casc-demo  | Jenkins initial setup is required...
jenkins-casc-demo  | Jenkins Configuration as Code is now available!
jenkins-casc-demo  | ...

4. Open your browser:

http://localhost:8080

5. Login with your configured credentials

The setup creates initial credentials (you can define them in jenkins.yaml). For basic setup, use whatever you configure in the YAML.

What You’ll See

When Jenkins finishes starting:

  • ✅ Jenkins UI is ready without admin setup wizard
  • ✅ System message displays your custom message
  • ✅ The job “hello-casc” already exists
  • ✅ No manual configuration needed
  • ✅ Everything came from the YAML files

Run the “hello-casc” job and it will confirm that JCasC is working correctly.

Key Configuration Options

Plugins

Edit plugins.txt to add or remove plugins:

configuration-as-code
job-dsl
pipeline-model-definition
git
slack:650.v303e0a8eba56  # Specify version

Security

Change user database in jenkins.yaml:

securityRealm:
  ldap:
    configurations:
      - server: "ldap.example.com"
        rootDN: "dc=example,dc=com"

Agents

Configure distributed builds:

jenkins:
  nodes:
    - permanent:
        name: "linux-agent"
        remoteFS: "/home/jenkins"
        numExecutors: 4
        mode: EXCLUSIVE

Important: Security and Credentials

⚠️ Security Warning

The example above stores credentials directly in YAML only for demonstration purposes. Never do this in production.

For real environments, use secure credential providers:

Option 1: Environment Variables

credentials:
  system:
    domainCredentials:
      - credentials:
          - basic:
              id: "prod-credentials"
              username: "jenkins-user"
              password: "${PROD_JENKINS_PASSWORD}"

Then set the environment variable:

export PROD_JENKINS_PASSWORD="your-actual-password-here"
docker compose up

Option 2: Docker Secrets (for Swarm)

credentials:
  system:
    domainCredentials:
      - credentials:
          - basic:
              id: "secret-creds"
              username: "jenkins"
              password: "${DB_PASSWORD_FILE:/run/secrets/db_password}"

Option 3: HashiCorp Vault

Integrate Vault credentials directly for enterprise deployments.

Option 4: Kubernetes Secrets

If running on Kubernetes, mount secrets as volumes:

volumes:
  - name: jenkins-secrets
    secret:
      secretName: jenkins-credentials
    mountPath: /var/jenkins_home/secrets

Extending Your Configuration

Add More Jobs

Edit jenkins.yaml to create additional jobs:

jobs:
  - script: >
      pipelineJob('build-app') {
        definition {
          cps {
            script('''
              pipeline {
                agent any
                stages {
                  stage('Build') {
                    steps {
                      sh 'npm install && npm build'
                    }
                  }
                }
              }
            ''')
          }
        }
      }      

Use External Job Definition Files

For complex setups, load jobs from separate files:

jobs:
  - script: >
      jobDSL {
        targets { "jobs/**/*.groovy" }
        removeAction { "DELETE" }
      }      

Then create jobs/my-pipeline.groovy with your job definitions.

System Tools Configuration

tool:
  git:
    installations:
      - name: "Default"
        home: "git"
  maven:
    installations:
      - name: "Maven-3.8.1"
        home: "/usr/share/maven"

Best Practices for JCasC

1. Version Control Everything

git init
git add .
git commit -m "Initial Jenkins JCasC setup"

Your entire Jenkins configuration is now in Git with full history.

2. Use Environment Variables for Secrets

password: "${ADMIN_PASSWORD}"

Set via:

  • Environment variables
  • .env file (Docker Compose)
  • Kubernetes secrets
  • CI/CD platform secrets

3. Keep It Readable

Add comments to help team members understand the configuration:

# Security settings
securityRealm:
  # Use LDAP for enterprise authentication
  ldap:
    configurations:
      - server: "ldap.company.com"

4. Start Simple, Grow Incrementally

Begin with a basic configuration and expand as you understand JCasC better. The example provided is intentionally simple but functional.

5. Test Locally First

Use Docker Compose locally to test changes before deploying to production:

# Test locally
docker compose up

# After changes, redeploy
docker compose up --build

Troubleshooting

Jenkins doesn’t start

Check logs:

docker compose logs jenkins

Configuration not applying

Verify CASC_JENKINS_CONFIG environment variable points to the correct file:

docker compose exec jenkins printenv | grep CASC

Plugin conflicts

Remove conflicting plugins from plugins.txt and rebuild:

docker compose down
# Edit plugins.txt
docker compose up --build

What’s Next?

From here, you can:

  • Scale to teams: Use different configuration files per environment (dev/staging/prod)
  • Integrate with Git: Store configuration in a Git repository with CI/CD pipeline
  • Deploy to Kubernetes: Convert this setup to Helm charts for cloud-native deployments
  • Add webhooks: Trigger configuration updates when your Git repo changes
  • Implement RBAC: Advanced role-based access control for larger teams

Conclusion

Jenkins Configuration as Code transforms Jenkins from a point-and-click UI nightmare into a version-controlled, repeatable, infrastructure-as-code setup. The example here is production-ready and works immediately out of the box.

Once you experience the simplicity of JCasC, going back to manual Jenkins configuration feels like driving a car without power steering. Your team will thank you for the clarity, your disaster recovery will be straightforward, and your Jenkins setup becomes as maintainable as your application code.

Start with the simple Docker example above, and scale up to match your team’s needs. The foundation is the same: YAML files, version control, and reproducible infrastructure.


Note: This setup example is validated and tested. All commands work as documented. If you encounter issues, check the Docker logs and ensure your YAML syntax is correct (YAML is whitespace-sensitive).