Swarm Agents Cloud

en ru

Jenkins Plugin Java 21+ License: MIT

A modern Jenkins plugin for dynamic agent provisioning on Docker Swarm clusters.

Why This Plugin?

This plugin is a modern replacement for the abandoned docker-swarm-plugin (last updated February 2021, 54+ unresolved issues).

Comparison with docker-swarm-plugin

Feature swarm-agents-cloud docker-swarm-plugin
Last Update 2025 (active) 2021 (abandoned)
Open Issues 54+
Java Version 21+ 8+
Jenkins Version 2.528.3+ Outdated
WebSocket Connection
JCasC Support ✅ Full
REST API ✅ Full CRUD
Prometheus Metrics
Docker Secrets/Configs
Template Inheritance
Rate Limiting
Health Checks
Pipeline DSL swarmAgent {}
Audit Logging
Orphan Cleanup ✅ Automatic
Dark Theme Dashboard
TLS Support ✅ Working ⚠️ Broken
XSS Vulnerabilities ✅ Fixed ⚠️ SECURITY-2811

Key Advantages

  • Modern Stack — Java 21, WebSocket, current Jenkins API
  • DevOps-Ready — JCasC, REST API, Prometheus, Pipeline DSL
  • Security — TLS, Secrets, Input Validation
  • Reliability — Rate Limiting, Retry with Backoff, Health Checks, Orphan Cleanup
  • Active Support — Regular updates vs abandoned project

Features

  • Dynamic Agent Provisioning — Automatically creates Docker Swarm services on demand
  • WebSocket Support — Modern agent connection (no inbound TCP ports required)
  • TLS/SSL Authentication — Full Docker TLS certificate support
  • Configuration as Code — Complete JCasC compatibility
  • Resource Management — CPU/memory limits and reservations per template
  • Health Checks — Configurable container health monitoring
  • Secrets & Configs — Docker Swarm secrets and configs integration
  • Rate Limiting — Built-in provisioning rate limits (thundering herd protection)
  • Dashboard — Real-time cluster monitoring at /swarm-dashboard
  • REST API — Programmatic management at /swarm-api
  • Template Inheritance — Inherit settings from parent templates (inheritFrom)
  • Prometheus Metrics/swarm-api/prometheus endpoint
  • Audit Logging — Track all provisioning events
  • Pipeline DSL — Native swarmAgent step for Jenkinsfiles
  • Retry with Backoff — Automatic exponential backoff on failures
  • Orphan Cleanup — Automatic cleanup of stale services

Requirements

Quick Start

Via UI

  1. Go to Manage JenkinsClouds
  2. Click New cloudDocker Swarm Agents Cloud
  3. Configure:
    • Docker Host: tcp://your-swarm-manager:2376
    • Credentials: Docker Server Credentials (for TLS)
    • Max Concurrent Agents: Limit total agents

Via Configuration as Code (JCasC)

jenkins:
  clouds:
    - swarmAgentsCloud:
        name: "docker-swarm"
        dockerHost: "tcp://swarm-manager:2376"
        credentialsId: "docker-tls-creds"
        maxConcurrentAgents: 10
        jenkinsUrl: "http://jenkins:8080/"
        swarmNetwork: "jenkins-agents"
        templates:
          - name: "maven"
            image: "jenkins/inbound-agent:latest"
            labelString: "maven docker"
            remoteFs: "/home/jenkins/agent"
            numExecutors: 2
            maxInstances: 5
            cpuLimit: "2.0"
            memoryLimit: "4g"
            connectionTimeoutSeconds: 300
            idleTimeoutMinutes: 30

Agent Templates

Field Description Default
name Template identifier Required
image Docker image jenkins/inbound-agent:latest
labelString Jenkins labels (space-separated)
remoteFs Agent working directory /home/jenkins/agent
numExecutors Executors per agent 1
maxInstances Max containers from template 5
cpuLimit CPU limit (e.g., "2.0")
memoryLimit Memory limit (e.g., "4g")
cpuReservation CPU reservation
memoryReservation Memory reservation

Advanced Options

Field Description
privileged Run with elevated privileges
oneShot Terminate the agent after a single completed build (requires numExecutors=1)
user Run as specific user (e.g., "1000:1000")
hostname Container hostname
entrypoint Custom container entrypoint
disableContainerArgs Don't pass args to entrypoint
capAdd / capAddString Linux capabilities to add (requires Docker Engine 20.10+ / API 1.41+)
capDrop / capDropString Linux capabilities to drop (requires Docker Engine 20.10+ / API 1.41+)
dnsServersString Custom DNS servers

Connection, Timeouts & Retry

These template fields control how long the plugin waits for an agent and how it retries provisioning. All are optional and fall back to the defaults below.

Field Description Default
connectionTimeoutSeconds Max time to wait for the agent to connect before giving up 300
idleTimeoutMinutes Idle time before a (non one-shot) agent is terminated 30
provisionRetryCount Number of provisioning retries on failure 3
provisionRetryDelayMs Initial delay between retries in ms (exponential backoff) 1000
templates:
  - name: "build"
    image: "jenkins/inbound-agent:latest"
    connectionTimeoutSeconds: 300
    idleTimeoutMinutes: 30
    provisionRetryCount: 3
    provisionRetryDelayMs: 1000

Health Checks

Attach a Docker container health check so the agent container is monitored while it runs. A health check is enabled only when healthCheckCommand is set — the plugin wraps the command as CMD-SHELL, so provide just the shell command.

Field Description Default
healthCheckCommand Shell command run inside the container to check health. Empty = no health check
healthCheckIntervalSeconds Time between checks 30
healthCheckTimeoutSeconds Time to wait for a single check before treating it as failed 10
healthCheckRetries Consecutive failures before the container is marked unhealthy 3
templates:
  - name: "with-healthcheck"
    image: "jenkins/inbound-agent:latest"
    healthCheckCommand: "curl -f http://localhost:8080/ || exit 1"
    healthCheckIntervalSeconds: 30
    healthCheckTimeoutSeconds: 10
    healthCheckRetries: 3

All of these fields are merged through template inheritance like the rest.

Template Inheritance

templates:
  - name: "base"
    image: "jenkins/inbound-agent:latest"
    cpuLimit: "2.0"
    memoryLimit: "4g"
    connectionTimeoutSeconds: 300

  - name: "maven"
    inheritFrom: "base"
    labelString: "maven docker"
    environmentVariables:
      - name: "MAVEN_OPTS"
        value: "-Xmx1g"

Inheritance is resolved across multiple levels, so you can build a hierarchy (base → intermediate → leaf) and each template only declares what it adds:

templates:
  - name: "base"                 # shared defaults for everything
    image: "jenkins/inbound-agent:latest"
    cpuLimit: "2.0"

  - name: "jvm"                  # adds JVM-specific settings
    inheritFrom: "base"
    environmentVariables:
      - name: "JAVA_OPTS"
        value: "-XX:MaxRAMPercentage=75"

  - name: "maven"               # leaf: inherits the whole base → jvm chain
    inheritFrom: "jvm"
    labelString: "maven"

Resolution rules:

  • Descendant values take precedence over ancestors; lists (environment variables, mounts, capabilities, etc.) are merged across the whole chain.
  • Cyclic references (aba) are detected and stopped with a warning instead of recursing forever.
  • Label matching uses the resolved template. A template with no effective labelString — neither its own nor an inherited one, like a base kept only for inheritance — matches only label-less jobs; it is not a catch-all and will not provision agents for jobs that require a specific label. Labels declared on an ancestor are inherited, so a leaf is matched by them too.

Ephemeral / One-Shot Agents

Set oneShot: true on a template to make agents self-destruct immediately after a single completed build, instead of being kept alive for the idle-timeout window. Useful when each build needs a guaranteed-clean filesystem and process state (security-sensitive jobs, workloads that leak temporary state, etc.).

templates:
  - name: "ephemeral-build"
    image: "jenkins/inbound-agent:latest"
    labelString: "ephemeral"
    numExecutors: 1            # Must be 1 with oneShot=true
    oneShot: true
    idleTimeoutMinutes: 1      # Fallback for the rare "agent connected but no build dispatched" case

Trade-offs:

  • Each build pays the full container provisioning cost (image pull + agent connection handshake), adding tens of seconds to queue time.
  • numExecutors must be 1. The form rejects oneShot=true combined with numExecutors > 1 because the one-shot retention strategy terminates the agent after the first completed build, which would abort any other build still running on a second executor.
  • If a parent template (referenced via inheritFrom) has oneShot: true, child templates inherit it and cannot disable it (same OR-merge as privileged).

Container Capabilities

capAdd / capDrop propagate Linux capabilities to the agent container's TaskSpec.ContainerSpec. Docker Engine 20.10 (API 1.41) or newer is required — older Engine versions silently ignore these fields.

templates:
  - name: "net-admin-agent"
    image: "jenkins/inbound-agent:latest"
    capAdd:
      - "CAP_NET_ADMIN"
    capDrop:
      - "CAP_CHOWN"

The newline-separated string form (capAddString) is also accepted for compatibility with existing docker-swarm-plugin configurations.

Docker Secrets

templates:
  - name: "with-secrets"
    secrets:
      - secretName: "my-secret"
        fileName: "secret.txt"
        targetPath: "/run/secrets"

Docker Configs

Docker Swarm configs work like secrets but are meant for non-sensitive configuration data. Only configName is required; the other fields are optional.

Field Description Default
configName Name of the existing Docker Swarm config Required
targetPath Mount path inside the container /{configName}
fileName File name at the target location Derived from targetPath
fileMode File permissions in octal (e.g. 0644)
uid / gid Owner UID / GID of the file inside the container
templates:
  - name: "with-configs"
    configs:
      - configName: "app-config"
        targetPath: "/etc/app/config.ini"
        fileName: "config.ini"
        fileMode: "0644"

Registry Authentication (Private Images)

Support for pulling images from private Docker registries when launching agents.

Supported Registries:

  • Docker Hub (public and private repositories)
  • Google Container Registry (gcr.io)
  • AWS Elastic Container Registry (ECR)
  • GitHub Container Registry (ghcr.io)
  • Azure Container Registry (azurecr.io)
  • Any private registry with username/password authentication

Setup Steps:

  1. Create Credentials in Jenkins:

    • Go to Manage JenkinsCredentials
    • Add Username with password credentials for your registry
    • Note the credentials ID
  2. Configure Template:

    • In template configuration, select credentials from Registry Credentials dropdown
    • Plugin automatically detects registry from image name

Configuration as Code Example:

jenkins:
  clouds:
    - swarmAgentsCloud:
        name: "docker-swarm"
        templates:
          - name: "private-agent"
            image: "myregistry.com/jenkins-agent:latest"
            registryCredentialsId: "docker-registry-creds"
            labelString: "private docker"

UI Configuration: Navigate to template settings → Registry Credentials dropdown → Select your credentials

Template Inheritance: Registry credentials can be inherited from parent templates:

templates:
  - name: "base-private"
    image: "myregistry.com/base:latest"
    registryCredentialsId: "docker-registry-creds"

  - name: "maven-private"
    inheritFrom: "base-private"
    image: "myregistry.com/maven:latest"
    # Inherits registryCredentialsId from base-private

Extra Hosts (Custom /etc/hosts Entries)

Add custom hostname-to-IP mappings to container's /etc/hosts file, equivalent to Docker's --add-host flag.

Use Cases:

  • Local development and testing
  • Custom DNS resolution for internal services
  • Database and service aliases

Format: hostname:IP (one entry per line, supports IPv4 and IPv6)

Configuration as Code Example:

templates:
  - name: "agent-with-hosts"
    image: "jenkins/inbound-agent:latest"
    extraHosts:
      - "database.local:10.0.0.50"
      - "internal-registry:192.168.1.100"
      - "api.internal:172.16.0.10"

UI Configuration: Navigate to template settings → Extra Hosts textarea → Enter hostname:IP pairs (one per line)

Example:

myhost:192.168.1.1
database:10.0.0.5
registry.local:172.16.0.20

Template Inheritance: Extra hosts are merged when using template inheritance:

templates:
  - name: "base"
    extraHosts:
      - "shared-db:10.0.0.1"

  - name: "maven"
    inheritFrom: "base"
    extraHosts:
      - "maven-repo:192.168.1.50"
    # Container will have both extra hosts entries

Validation:

  • Automatic validation of IP addresses (IPv4 and IPv6)
  • Hostname format checking
  • Clear error messages for invalid entries

Node Mode

mode controls which jobs the template's agents accept, mirroring Jenkins node usage modes.

Value Behaviour
NORMAL (default) Use this template as much as possible; also serves label-less jobs
EXCLUSIVE Only run jobs whose label expression matches the template's labels
templates:
  - name: "gpu"
    labelString: "gpu"
    mode: EXCLUSIVE        # reserved exclusively for jobs that ask for "gpu"

Environment Variables

Inject environment variables into the agent container. name and value are both required; the list is merged across template inheritance.

templates:
  - name: "maven"
    environmentVariables:   # alias: envVars
      - name: "MAVEN_OPTS"
        value: "-Xmx2g -XX:+UseG1GC"
      - name: "JAVA_HOME"
        value: "/opt/java/openjdk"

Volumes & Mounts

Mount host paths, named volumes, or tmpfs into the agent container.

Field Description Default
type BIND (host path), VOLUME (named volume), or TMPFS Required
source Host path or volume name Required
target Mount path inside the container Required
readOnly Mount the source read-only false
templates:
  - name: "maven"
    mounts:                 # alias: hostBinds
      - type: BIND
        source: "/var/cache/maven"
        target: "/root/.m2/repository"
        readOnly: false
      - type: VOLUME
        source: "jenkins-workspace"
        target: "/workspace"

Build Cache Directories

cacheDirs lists container paths (each must start with /) to preserve between builds for faster incremental work. Entries are merged across template inheritance.

templates:
  - name: "maven"
    cacheDirs:
      - "/root/.m2/repository"
      - "/tmp/build-cache"

Published Ports

Publish container ports on the Swarm ingress network. Format [hostPort:]containerPort[/protocol]; an empty host port assigns a random one, and the protocol defaults to tcp.

templates:
  - name: "with-ports"
    portBindings:           # alias: portBinds (newline-separated string form)
      - "8080:8080"         # host 8080 -> container 8080
      - ":5900"             # random host port -> container 5900
      - "443:8443/tcp"      # explicit protocol

Placement Constraints & Network Aliases

placementConstraints pin agent tasks to specific Swarm nodes (Docker --constraint syntax); networkAliases add extra DNS aliases on the Swarm network. Both are lists and are merged across template inheritance.

templates:
  - name: "build"
    placementConstraints:
      - "node.role==worker"
      - "node.labels.type==build"
    networkAliases:
      - "build-agent"

DNS Configuration

dnsServers sets custom resolvers, dnsSearch adds search domains, and dnsOptions passes resolver options. All are lists merged across inheritance.

templates:
  - name: "build"
    dnsServers:             # alias: dnsServersString / dnsIps (comma-separated)
      - "10.0.0.2"
    dnsSearch:
      - "internal.example.com"
    dnsOptions:
      - "ndots:2"

Kernel Parameters (sysctls)

Set kernel parameters on the container (Docker --sysctl). Entries are key=value and merged across inheritance.

templates:
  - name: "tuned"
    sysctls:                # alias: sysctlsString (newline-separated)
      - "net.core.somaxconn=1024"
      - "net.ipv4.tcp_syncookies=0"

Container Stop Behaviour

Control how the container is stopped when the agent is removed.

Field Description Default
stopSignal Signal sent to stop the container (e.g. SIGTERM, SIGINT) Docker default (SIGTERM)
stopGracePeriod Seconds to wait before force-killing the container 10
templates:
  - name: "graceful"
    stopSignal: "SIGTERM"
    stopGracePeriod: 30

Security Profiles

Apply seccomp and AppArmor profiles to the agent container (Docker Engine 29+).

Field Accepted values
seccompProfile default, unconfined, or a path to a custom profile
apparmorProfile runtime/default, unconfined, or a custom profile name
templates:
  - name: "hardened"
    seccompProfile: "default"
    apparmorProfile: "runtime/default"

Generic Resources (GPU)

Request Swarm generic resources such as GPUs. Each entry is Kind=value; nodes must advertise the matching resource. Entries are merged across inheritance.

templates:
  - name: "ml"
    labelString: "gpu"
    genericResources:       # alias: genericResourcesString ("NVIDIA-GPU=1, FPGA=2")
      - kind: "NVIDIA-GPU"
        value: 1

Pipeline DSL

Declarative pipeline (agent { swarmAgent { ... } })

The swarmAgent directive is registered with the Pipeline Syntax → Declarative Directive Generator → agent, so the new plugin is a drop-in replacement for classic docker-swarm-plugin usage:

pipeline {
    agent {
        swarmAgent {
            cloud 'docker-swarm'
            template 'maven'
            label 'maven java'
        }
    }
    stages {
        stage('Build') {
            steps { sh 'mvn clean package' }
        }
    }
}

If only one Docker Swarm cloud is configured, cloud may be omitted. The label defaults to the referenced template's labelString when not given. Inline configuration without a template is also supported:

pipeline {
    agent {
        swarmAgent {
            image 'jenkins/inbound-agent:alpine'
            label 'docker'
            cpuLimit '2.0'
            memoryLimit '4g'
        }
    }
    stages { ... }
}

Scripted pipeline (swarmAgent { ... } step)

pipeline {
    agent none
    stages {
        stage('Build') {
            steps {
                swarmAgent(cloud: 'docker-swarm', template: 'maven') {
                    sh 'mvn clean package'
                }
            }
        }
    }
}

Image Requirements / Custom Images

When the plugin provisions an inbound agent, it passes Jenkins connection details to the container in two parallel ways so most images "just work":

  • Environment variables (always set):
    • JENKINS_URL, JENKINS_AGENT_NAME, JENKINS_SECRET, JENKINS_AGENT_WORKDIR
    • JENKINS_WEB_SOCKET=true (WebSocket inbound protocol)
    • Aliases for compatibility: JNLP_URL, JENKINS_JNLP_URL, DOCKER_SWARM_PLUGIN_JENKINS_AGENT_JNLP_URL, DOCKER_SWARM_PLUGIN_JENKINS_AGENT_SECRET
  • Command-line args (positional): [<JENKINS_URL>, <SECRET>, <AGENT_NAME>], appended to the image's ENTRYPOINT.
templates:
  - name: default
    image: "jenkins/inbound-agent:alpine"   # or :latest, :jdk21, ...
    labelString: "docker"

jenkins/inbound-agent ships with ENTRYPOINT ["/usr/local/bin/jenkins-agent"] that accepts the positional args correctly — no extra configuration needed.

Custom images without a Jenkins-compatible ENTRYPOINT

If your image has no ENTRYPOINT (or one that does not understand the url secret name argument convention), the positional args become the container's whole command. Docker will then try to exec the URL as a binary and fail with:

OCI runtime create failed: ... exec: "https://<jenkins>/": no such file or directory

Two equivalent fixes — pick whichever fits your image:

Option A — let the plugin set a real ENTRYPOINT (entrypoint: takes a free-form command, args are skipped):

templates:
  - name: custom
    image: "my-registry/my-agent:1.2.3"
    entrypoint: "/usr/local/bin/my-startup.sh"

Option B — use env vars only (the image must read $JENKINS_URL, $JENKINS_SECRET, $JENKINS_AGENT_NAME itself):

templates:
  - name: custom
    image: "my-registry/my-agent:1.2.3"
    disableContainerArgs: true   # skip positional [url, secret, name] args

disableContainerArgs is the safest default for slim or non-standard images.

REST API

Base URL: http://jenkins/swarm-api/

Method Endpoint Description
GET /clouds List all clouds
GET /cloud?name=X Cloud details
GET /templates?cloud=X List templates
GET /template?cloud=X&name=Y Template details
GET /agents?cloud=X List agents
GET /prometheus Prometheus metrics
GET /audit?cloud=X Audit log
POST /provision?cloud=X&template=Y Provision agent
PUT /template Update template

Dashboard

Access at http://jenkins/swarm-dashboard/

  • Cluster health overview
  • Node status and resources
  • Active services list
  • Dark theme support

Prometheus Metrics

http://jenkins/swarm-api/prometheus

Metrics: swarm_agents_total, swarm_agents_active, swarm_nodes_total, swarm_memory_total_bytes, etc.

Troubleshooting

Enable Debug Logging

Manage JenkinsSystem Log → Add logger io.jenkins.plugins.swarmcloud with level FINE

Common Issues

Error Solution
"Unsupported protocol scheme: https" Use tcp:// not https://
OCI runtime create failed: ... exec: "https://...": no such file or directory Image has no ENTRYPOINT. Use jenkins/inbound-agent, set entrypoint: on the template, or set disableContainerArgs: true. See Image Requirements. The plugin also surfaces a HINT: line in the build log on this failure.
"Connection refused" Check Docker API is exposed
"TLS handshake failed" Configure Docker Server Credentials
"This node is not a swarm manager" Run docker swarm init

Building from Source

git clone https://github.com/jenkinsci/swarm-agents-cloud-plugin.git
cd swarm-agents-cloud-plugin
mvn clean package -DskipTests
# Result: target/swarm-agents-cloud.hpi

Contributing

See CONTRIBUTING.md

License

MIT License — see LICENSE