Jenkins is an open-source CI/CD tool widely used in DevOps. This guide goes through installing Jenkins, using secrets in Jenkins, and writing a Jenkinsfile to pull and push a Docker image to an Oracle Cloud Container Registry.
What is CI/CD?
An important part of a good development practice is a good continuous integration/continuous development (CI/CD) practice, which focuses on the deployment of your app's updates for the public to use.
A CI/CD pipeline can be broken into two parts (obviously):
- Integration
Deals with integrating the new code to the central codebase. This includes checks (lint/format checks, build checks, basic unit tests, secret exposures) and integration steps to the codebase like pulling in external code and requesting reviews.
2. Deployment
Things like building a Docker image, pushing it, and pushing out deployment update in a safe manner, possibly using A/B testing
A lot of these like running tests are repetitive and can therefore be automated, e.g., on every new code push.
Enter Jenkins.
If you've heard of GitHub Actions, Jenkins is the lower-level, open-source sister of GitHub Actions, allowing far more customizations.
Installing Jenkins
Jenkins is a standalone tool that you can install and deploy on your own, by default on http://localhost:8080.
See installation instructions in the official Jenkins page.
Your Jenkins homepage (localhost:8080) looks somewhat like this:
Running a basic Shell command in Jenkins
Let's start small and make a build job that prints to the console.
From the homepage, click New Item > Freestyle project (remember to name your project).
Once you created the project and arrive to the Configuration page, scroll down to Build Steps and add an Execute shell step.
Put something like:
echo "hello123"
and click Save.
At the project page, click Build now and see the Build History appear with your build #1. Click it.
Click Console Output and see your echo being printed in the console:
Storing Secrets
Storing secrets to later inject into your build is easy in Jenkins. To create a new secret, click on your Profile Picture > Credentials > (global) > Add Credentials.
You can add username/password combinations, text secrets, file secrets, and more.
Let's try adding a .env
file as an example. Since on Mac I can't select a .env
file from Finder, I'll be renaming it to fenv
. Let's give the credential an ID of fd-env-f
.
And here, I'll be adding my Oracle Container Registry Docker login username and password i.e. Auth Token. You can read more about using the Container Registry in another article.
A full Docker push example with Jenkinsfile
Our goal for this last part is to understand this Jenkinsfile
:
// Jenkinsfile
pipeline {
agent any
stages {
stage('Clean workspace') {
steps {
cleanWs()
}
}
stage('Git pull after each commit') {
steps {
git branch: 'dev', credentialsId: 'fd-git-creds', url: 'https://github.com/farisdurrani/nonexistentrepo'
echo 'pulled successfully'
}
}
stage('Build and Push Docker Frontend Image') {
steps {
withCredentials(
[file(credentialsId: 'fd-env-f', variable: 'mapEnvF')]
) {
script {
docker.withRegistry(registryUrl, registryCredential) {
// create .env file
sh "sudo touch ./frontend/.env"
sh "sudo chmod 666 ./frontend/.env"
sh "cp \"$mapEnvF\" ./frontend/.env"
// build image, push to registry, and remove image
image = docker.build(
"$frontendImage",
"./frontend"
)
image.push(tagLatest)
sh "docker rmi ${image.id}"
}
}
}
}
}
}
environment {
tenancyNamespace = 'id8wj7jebvoqh'
registry = 'iad.ocir.io'
registryCredential = "fd-docker-oci-container-registry"
registryUrl = "https://$registry/"
tagPrefix = "$registry/$tenancyNamespace"
frontendImage = "$tagPrefix/myapp-f:latest"
// required to save/re-use Yarn cache across multiple runs
YARN_GLOBAL_FOLDER = "${JENKINS_HOME}/workspace/.yarn/global"
}
}
This file is the Jenkins build pipeline code of this app, usually stored at the root directory of the app repo and conventionally named Jenkinsfile
without extensions.
This file has 3 stages, each with its own steps:
- Clean up workspace - delete all previous build files that were created in the previous builds
- Git pull - pulling the code from the repo
- Build and push Docker image to the Oracle Container Registry
The first two are relatively straightforward. Notice we're using a credentialsId
, created in Credentials, to login to the git repo.
The goal of the third step is to build and push the Docker image while injecting a .env
file into the image.
And yes, you should be using Docker's built-in --build-arg
flag to do this but we're more concerned with the base Jenkins functionality here.
Using the withCredentials
function, we are able to pick the file secret using its ID and copy its contents into the Docker image before push. Due to the restrictive nature of Jenkins secrets, a little copy-pasting is necessary to get its raw contents.
Copy-pasting the Jenkinsfile
contetns into a new Jenkins Pipeline (not a Freestyle project), we have prepared a proper pipeline.
Alternatively, you may also choose to have the Jenkins pipeline refer to a Jenkinsfile
stored in a git repo, by choosing Pipeline script from SCM
as the Definition of the pipeline:
Save and run the job. Here is an example of what many Jenkins build jobs look like:
Important: Docker permissions
You may encounter this issue when using Docker in Jenkins:
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
To solve this I refer to this StackOverflow answer which suggests running in your native terminal every time your computer starts up:
sudo chmod 666 /var/run/docker.sock
Important: "
vs '
in Jenkinsfile
There is a strong distinction between double-quotes "
and single-quotes '
in a Jenkinsfile. "
enables string interpolation, i.e.,"docker rmi ${image.id}"
while '
does not, making it return the raw string as it is without substitution.
Building with a schedule
To run the Jenkins build pipeline at a schedule with every code changes, simply add a Poll SCM option under Build Triggers, for example,
H/30 * * * *
to run the build job every 30 minutes. If no changes to the remote code are detected, the pipeline is not executed.
Safe harbor statement
The information provided on this channel/article/story is solely intended for informational purposes and cannot be used as a part of any contractual agreement. The content does not guarantee the delivery of any material, code, or functionality, and should not be the sole basis for making purchasing decisions. The postings on this site are my own and do not necessarily reflect the views or work of Oracle or Mythics, LLC.
This work is licensed under a Creative Commons Attribution 4.0 International License.