---
title: "CI Shootout: Getting started with Jenkins, Concourse, Tekton and Argo Workflows"
description: "How difficult is it to get from not having a CI system to having one? Compare them against each other: Jenkins, Concourse, Tekton and Argo Workflows."
image: https://blog.container-solutions.com/hubfs/gunfight-CI-CD.jpeg
---

Check out our **[Cloud Native Services](https://www.container-solutions.com/services) **and** **book a call with one of our experts today! 

[![Container Solutions](https://blog.container-solutions.com/hubfs/CS_blog/logo-main.svg "Container Solutions")](http://container-solutions.com)

- share:
- [**](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fblog.container-solutions.com%2Fci-shootout-getting-started-with-jenkins-concourse-tekton-and-argo-workflows)
- [**](https://www.twitter.com/share?url=https%3A%2F%2Fblog.container-solutions.com%2Fci-shootout-getting-started-with-jenkins-concourse-tekton-and-argo-workflows)
- [**](http://www.linkedin.com/shareArticle?mini=true&url=https://blog.container-solutions.com/ci-shootout-getting-started-with-jenkins-concourse-tekton-and-argo-workflows)
- [**](https://blog.container-solutions.com/ci-shootout-getting-started-with-jenkins-concourse-tekton-and-argo-workflows#)

[![](https://blog.container-solutions.com/hubfs/gunfight-CI-CD.jpeg) ](https://blog.container-solutions.com/ci-shootout-getting-started-with-jenkins-concourse-tekton-and-argo-workflows)

[Kubernetes](https://blog.container-solutions.com/tag/kubernetes), [Continuous Integration](https://blog.container-solutions.com/tag/continuous-integration)

# CI Shootout: Getting started with Jenkins, Concourse, Tekton and Argo Workflows

[Tom Godkin](https://blog.container-solutions.com/author/tom-godkin)

 October 4, 2023

 18 minutes Read

At EngineerBetter we’ve evaluated four self-hosted CI systems in order to compare them against each other: [Jenkins](https://www.jenkins.io/), [Concourse](https://concourse-ci.org/), [Tekton](https://tekton.dev/) and [Argo Workflows](https://argoproj.github.io/workflows/). For each CI system we’ve created some pipelines that:

- Run pre-deployment static analysis over various forms of Infrastructure as Code
- Deploy a [Kubernetes](https://kubernetes.io/) cluster to a staging environment
- Deploy the [Sock Shop](https://github.com/microservices-demo/microservices-demo) application to the cluster
- Run an acceptance test on the deployed application
- Promote the deployment by triggering an identical production pipeline

Across a series of blog posts, we’ll be evaluating how each CI system supports the following use cases:

- *This post* - [1. **Install** and configure the CI system](https://engineerbetter.com/blog/ci-shootout-getting-started/#1-install-and-configure-the-ci-system)
- *This post* - [2. **Run** a “Hello, World” task](https://engineerbetter.com/blog/ci-shootout-getting-started/#2-run-a-hello-world-task)
- [*Second post*](https://engineerbetter.com/blog/ci-shootout-inputs-and-outputs) - 3. **Trigger** when external resources (eg Git repos, S3 buckets) change
- [*Second post*](https://engineerbetter.com/blog/ci-shootout-inputs-and-outputs) - 4. Use **inputs and outputs to tasks**
- [*Second post*](https://engineerbetter.com/blog/ci-shootout-inputs-and-outputs) - 5. Write **outputs externally** (like making a Git commit or pushing a file to S3)
- [*Third post*](https://engineerbetter.com/blog/ci-shootout-reuse) - 6. **Re-use** pipeline configuration
- [*Third post*](https://engineerbetter.com/blog/ci-shootout-reuse) - 7. Use the CI system as a **build monitor**
- [*Final post*](https://engineerbetter.com/blog/ci-shootout-conclusion) - 8. Conclusion

In this post we’ll explore the first two of the use cases listed above. In each section we’ll give each CI system a rating of either *Poor*, *Mediocre*, *Good* or *Great*.

## 1. Install and Configure the CI system

Just how difficult is it to get from not having a CI system to having one? Here we answer that question by exploring how much configuration was needed and the amount of manual work required in each case. We deployed each CI system to an already-existing Kubernetes.

### Jenkins - *Mediocre*

We used [Helm to install Jenkins in a single command](https://github.com/EngineerBetter/iac-example/tree/main/helmfile.d); installation itself was seamless and trivial. Configuration of Jenkins was less trivial and we found ourselves wanting to [customise Jenkins’ default configuration](https://github.com/EngineerBetter/iac-example/blob/main/config/default/jenkins.yaml). Jenkins [configuration ships with mostly good defaults](https://github.com/jenkins-infra/charts/blob/master/config/default/jenkins-infra.yaml), but at ~600 lines of YAML that default configuration can be daunting when you inevitably need to change something. For example, we wanted to be explicit about which plugins we required (via Infrastructure as Code) such as Blue Ocean (a prettier pipeline UI), so we specified that in [our configuration file](https://github.com/EngineerBetter/iac-example/blob/8032c1a299e4d691b72831c46a272ec5550d9f8a/config/default/jenkins.yaml).

```
# Create a helmfile.d directory with the below helmfile config# Custom Jenkins configuration is linked in the above paragraphhelmfile apply
```

```
---helmDefaults:  atomic: false  force: false  timeout: 480  wait: truereleases:- name: jenkins  chart: jenkins/jenkins  version: 3.3.18  namespace: jenkins  values: ["../config/default/jenkins.yaml"]  set:  - name: namespace  value: jenkins
```

```
repositories:- name: jenkins  url: https://charts.jenkins.io
```

 

### Concourse - *Great*

We followed [Concourse’s own guide to installing Concourse via a Helm chart](https://github.com/concourse/concourse-chart). Installation was simple and it worked without issue.

```
helm repo add concourse https://concourse-charts.storage.googleapis.com/helm install concourse concourse/concourse
```

 

### Tekton - *Good*

We followed [Tekton’s Getting Started guide](https://tekton.dev/docs/getting-started/) to install Tekton via four kubectl commands that reference publicly available Kubernetes YAML files. It worked for us out of the box without tweaking anything and was ready within a minute or two.

Tekton required installation of four separate components for our use case: [pipelines](https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml), [dashboard](https://storage.googleapis.com/tekton-releases/dashboard/latest/tekton-dashboard-release.yaml), [triggers](https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml) and [interceptors](https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml). I’d guess that it’d be the exception to only want to install a subset of these four so perhaps the Getting Started experience could be improved by providing a single Kubernetes YAML containing all four.

```
export tekton_releases="https://storage.googleapis.com/tekton-releases"kubectl apply --filename "${tekton_releases}/pipeline/latest/release.yaml"kubectl apply --filename "${tekton_releases}/dashboard/latest/tekton-dashboard-release.yaml"kubectl apply --filename "${tekton_releases}/triggers/latest/release.yaml"kubectl apply --filename "${tekton_releases}/triggers/latest/interceptors.yaml"
```

 

### Argo Workflows - *Great*

We followed [Argo Workflows’ own Quick Start instructions](https://argoproj.github.io/argo-workflows/quick-start/) to install Argo Workflows via a few kubectl commands that referenced [publicly available Kubernetes YAML files](https://raw.githubusercontent.com/argoproj/argo-workflows/stable/manifests/quick-start-postgres.yaml). Unlike Tekton, there is only a single YAML manifest to apply. It worked for us out of the box without tweaking anything and was ready within a minute or two.

```
export manifests="https://raw.githubusercontent.com/argoproj/argo-workflows/stable/manifests"kubectl create namespace argokubectl apply --namespace argo --filename "${manifests}/quick-start-postgres.yaml
```

 

### Summary

The **easiest installation experience was with Concourse and Argo Workflows**, Tekton came in at a close second due to the additional context requirements of needing to install several components.

**Jenkins falls far behind** the other options here due to the difficulty we had in understanding how to define everything about the installation (ie including plugins) in code.

## 2. Run a “Hello, World” task

Now that we have a CI system, how difficult is it to run our first task? Here we’ll demonstrate how hard it was to write the task definition and then configure it in the CI system.

### Jenkins - *Poor*

Being the oldest of the CI systems under evaluation, it’s not surprising that the way in which pipelines are written has been revisited a few times in its history. We wrote a “Hello World” job using the most modern pipeline syntax that Jenkins offers: [Declarative Pipelines](https://www.jenkins.io/doc/book/pipeline/syntax/).

Jenkins pipelines are configured in two ways: manually through the UI or by submitting declarative XML using the Jenkins CLI. We chose to use the latter so that we can source control our pipeline definitions.

The configuration references *another* file containing the bulk of the pipeline definition (often called a Jenkinsfile) which is written using the Declarative Pipelines DSL. There is a level of redundancy between these two files. It’s also possible to change the configuration of the pipeline within the Jenkinsfile that would result in the XML stored in source control differing from the one actually configured on Jenkins after a pipeline run.

Although we could achieve source controlled pipeline configuration, there were two ‘flies in the ointment’.

Firstly, the XML file was extremely difficult to understand and author, and so we found it easier to configure our pipeline via Jenkins’ UI and then use the CLI’s “get-job” subcommand to then store the generated configuration in source control.

Secondly, the Jenkins CLI has separate “create-job” and “update-job” subcommands, which means any automation that sets pipelines has to figure out if it’s already set to know which command to use.

Defining the “Hello World” pipeline using the Declarative Pipeline syntax was trivial, and it is also easy to read. Executing the task is trivial via the Jenkins UI.

```
# First deployjava \  -jar "$JENKINS_CLI_JAR" \    -s "$JENKINS_URL" \    -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \    create-job 'Hello World' \  < pipeline.xml# Subsequent deploysjava \  -jar "$JENKINS_CLI_JAR" \    -s "$JENKINS_URL" \    -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \    update-job 'Hello World' \  < pipeline.xml
```

*pipeline.Jenkinsfile*

```
def ciImage = 'engineerbetter/iac-example-ci:15-promote'pipeline {  agent {    kubernetes {      yaml """      apiVersion: v1      kind: Pod      spec:        containers:        - name: iac        image: ${ciImage}        command:        - cat        tty: true      """.stripIndent()      defaultContainer 'iac'    }  }  stages {    stage('Hello, World!') {      steps {        sh 'echo "Hello, World!"'      }    }  }}
```

*pipeline.xml*

```
<?xml version='1.1' encoding='UTF-8'?><flow-definition plugin="workflow-job@2.40">  <actions>    <org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobAction    plugin="pipeline-model-definition@1.8.4"/>    <org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobPropertyTrackerAction    plugin="pipeline-model-definition@1.8.4">      <jobProperties/>      <triggers/>      <parameters/>      <options/>    </org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobPropertyTrackerAction>  </actions>  <description></description>  <keepDependencies>false</keepDependencies>  <properties>    <jenkins.model.BuildDiscarderProperty>      <strategy class="hudson.tasks.LogRotator">        <daysToKeep>-1</daysToKeep>        <numToKeep>200</numToKeep>        <artifactDaysToKeep>-1</artifactDaysToKeep>        <artifactNumToKeep>-1</artifactNumToKeep>      </strategy>    </jenkins.model.BuildDiscarderProperty>    <org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty/>  </properties>  <definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition"  plugin="workflow-cps@2.90">    <scm class="hudson.plugins.git.GitSCM" plugin="git@4.7.1">      <configVersion>2</configVersion>      <userRemoteConfigs>        <hudson.plugins.git.UserRemoteConfig>          <url>my_repo_url</url>          <credentialsId>git</credentialsId>        </hudson.plugins.git.UserRemoteConfig>      </userRemoteConfigs>      <branches>        <hudson.plugins.git.BranchSpec>          <name>main</name>        </hudson.plugins.git.BranchSpec>      </branches>      <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>      <submoduleCfg class="empty-list"/>      <extensions/>    </scm>    <scriptPath>pipeline.Jenkinsfile</scriptPath>    <lightweight>true</lightweight>  </definition>  <triggers/>  <disabled>false</disabled></flow-definition>
```

 

### Concourse - *Great*

Concourse has the best problem domain abstraction of the four systems under evaluation. [Pipelines](https://concourse-ci.org/pipelines.html) and [Tasks](https://concourse-ci.org/tasks.html) are configured by YAML, but unlike Tekton and Argo Workflows the YAML configuration is bespoke to Concourse and not made of Kubernetes resources - which makes sense since Concourse existed before Kubernetes was popular.

Generally speaking task definitions are not ‘applied’ to Concourse, instead Concourse pipelines will look for them at runtime based on the pipeline configuration. Tasks can also be executed directly from Concourse’s CLI, “[fly](https://concourse-ci.org/fly.html)” (which can be downloaded from the UI of your Concourse).

Since Concourse is not Kubernetes native, there’s a step required in order to authenticate with Concourse the first time you interact with it. You can authenticate using your GitHub account, amongst other options.

```
fly --target ci login --concourse-url "$CONCOURSE_URL"fly --target ci execute --config hello.yaml
```

```
platform: linuximage_resource:  type: registry-image  source: {repository: busybox}run:  path: echo  args: ["Hello, World!"]
```

 

### Tekton - *Great*

Everything about Tekton is Kubernetes native, so it follows that defining a [Tekton Task](https://tekton.dev/docs/pipelines/) is as simple as creating an appropriate Kubernetes YAML file and applying it. Tekton stores defined Tasks as Kubernetes resources and executes them by applying another resource known as a TaskRun. Since it’s all Kubernetes native, source controlling these tasks is easily achievable but it also means there’s a bit of boilerplate since the abstraction over Kubernetes is (deliberately) pretty shallow.

Likewise, Tekton Pipelines are a Kubernetes resource that represents a sequence of Tasks to be executed. PipelineRuns are the resources that execute Pipelines.

Tasks and Pipelines may also be executed by the Tekton CLI (which proxies authentication via the active kubeconfig) rather than defining and applying a TaskRun or PipelineRun.

```
kubectl apply --filename hello-task.yamltkn task start hello-world-tasktkn taskrun logs --last --follow
```

```
apiVersion: tekton.dev/v1beta1kind: Taskmetadata: {name: hello-world-task}spec:  steps:  - name: hello-world  image: docker/whalesaycommand: [echo, "Hello, World!"]
```

 

### Argo Workflows - *Great*

As with Tekton, resources in Argo Workflows are simply shallow abstractions over Kubernetes resources. Kubernetes YAML is used to define [Argo Workflows’ Templates](https://argoproj.github.io/argo-workflows/workflow-templates/). Templates are then composed into Workflows using another `Kind` of Kubernetes resource containing a directed acyclic graph (DAG) syntax when there’s more than one template. The Workflows are then executed using the Argo Workflows CLI.

Unlike Tekton Tasks and Pipelines, WorkflowTemplates cannot be executed outside the context of a Workflow; the amount of configuration required to execute the Hello World template is still trivial.

```
argo --namespace argo submit hello-workflow.yaml --watchargo --namespace argo logs @latest
```

```
apiVersion: argoproj.io/v1alpha1kind: Workflowmetadata: {generateName: hello-workflow-}spec:  entryPoint: hello  templates:  - name: hello  container:    image: busybox    command: [echo, hello]
```

 

### Summary

Getting started with Concourse, Tekton and Argo required very little effort; each required at most 10 lines of YAML and a few commands to execute. Looking at the output of each was similarly trivial.

Both Jenkins and Argo Workflows required the execution of a pipeline (or Workflow) in order to execute a single task. This didn’t add to the complexity in the case of Argo Workflows, however executing Hello World on Jenkins required orders of magnitude more configuration than the other options and contained a gotcha in the form of the non-idempotent commands.

Jenkins again fell far behind the competition in this use case with the differences between the Concourse, Tekton and Argo Workflows being negligible.

## Scorecard

 

## Next time

In [the next blog in this series](https://engineerbetter.com/blog/ci-shootout-inputs-and-outputs) we explore the following three use cases that we first described in the intro:

- *Trigger pipeline runs when external resources (eg Git repos, S3 buckets) change*
- *Use inputs and outputs to tasks*
- *Write outputs externally (like making a Git commit or pushing a file to S3)*

[### Cloud Foundry Rolling Deployments

](https://blog.container-solutions.com/cloud-foundry-rolling-deployments)

[![prev-arrow](https://cdn2.hubspot.net/hubfs/3842749/Wow%202019/Images/left-arrow.png) Previous article](https://blog.container-solutions.com/cloud-foundry-rolling-deployments)

[ Next article ![next-arrow](https://cdn2.hubspot.net/hubfs/3842749/Wow%202019/Images/right-arrow.png) ](https://blog.container-solutions.com/cloud-cost-management-part-i-orientation)

[

### Cloud Cost Management Part I - Orientation

](https://blog.container-solutions.com/cloud-cost-management-part-i-orientation)

Comments

Leave your Comment

![cs-logo-white](https://blog.container-solutions.com/hs-fs/hubfs/cs-logo-white.png?width=150&height=61&name=cs-logo-white.png "cs-logo-white")

#### **Talk to sales**

[info@container-solutions.com](mailto:info@container-Solutions.com)

#### Stay In Touch

- [**](https://www.linkedin.com/company/container-solutions/)
- [**](https://twitter.com/containersoluti)

© 2024 Container Solutions