Jenkins – Jenkins Pipeline for SpringBoot Application JFrog + Docker + Kubernetes + Azure ACS & AKS
1.Configure SpringBoot Project
We have SpringBoot application in Github.
https://github.com/smlcodes/Docker-SpringBoot-EmployeeServices.git
we have removed MySQL dependency from docker because we do not require separate MySQL instance for each Image.
Build & Run the GitHub code.
2.Create MySQL Database in Azure
3.Manual Docker Image for SpringBoot Project
To containerize an application, we enclose our application inside a container image and publish that image to a shared registry. The container runtime pulls this image from the registry, unpacks the image, and runs the application inside it.
a. Building a Container Image the Conventional Way
1.Creating a Dockerfile
A Dockerfile
is a plain text file that contains instructions from which a
Docker image is built. Create a Dockerfile
in the project’s root folder.
A Dockerfile
contains the commands below.
-
FROM: directive sets the image on which the container will be based on.
-
RUN: executes commands in the container.
-
COPY: creates a copy of files from the file system in the container.
-
CMD: sets the executable commands within the container.
# Image based on JAVA Container
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} Docker-EmployeeServices-1.0.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/Docker-EmployeeServices-1.0.jar"]
2.Building the Application
We first build the application with Maven. This creates an executable JAR of the application. We need to convert this executable JAR into a Docker image for running in a Docker engine.
mvn clean package
3. Building the Container Image
We need to create Docker image from jar file by running docker build -t
Repository:TAGVERSION
docker build -t employeeservices:latest .
We can see our image listed with the command:
docker images
4.Viewing the Layers Inside the Container Image
We will use the dive tool to view check
layers inside the image. using dive Repository:TAGVERSION
5.Running Docker Image
docker run --name employeeservices -d -p 8080:8080 employeeservices:latest
6.Execute the command below to list all the running Docker containers.
docker container ps
7.Access Running application by docker URL
b. Building Using Maven
By using Maven, we have multiple options.
-
Using the integrated Spring Boot build-image goal.
-
Using the jib-maven-plugin from Google.
-
Using the dockerfile-maven-plugin from Spotify.
1.Using the integrated Spring Boot build-image goal.
Spring Boot comes pre-shipped with its own plugin for building Docker images and you don’t need to make any changes. It is available through the standard spring-boot-starter-parent that is included in your pom.xml.
you do not need to write a Dockerfile either and the plugin takes care of Spring-recommended security, memory, and performance optimizations. if there is a Dockerfile located within your source code repository, it will be ignored.
To create Docker image, just run
mvn spring-boot:build-image
make sure project name must be on small letters & no special symbols
2. Using the jib-maven-plugin from Google
Jib is a Maven and Gradle plugin for creating Docker images for Java applications.
-
it does not require Docker to be installed locally
-
it does not require you to have written a Dockerfile
-
Best Choice for Continuous Integration / build server
-
jib-maven-plugin will build and push the image straight to the Docker registry of choice.
**Using Local System**
Since we are not pushing to a Docker registry, we will tell Jib to use our local
Docker installation by using the dockerBuild goal.
pom.xml
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>2.6.0</version>
<configuration>
<from>
<image>gcr.io/distroless/java:8</image>
</from>
<to>
<image>docker-employeeservices:${project.version}</image>
</to>
</configuration>
</plugin>
# Builds to a container image and pushes registry.
$ mvn compile jib:build
# Builds to a Docker daemon in local system.
$ mvn compile jib:dockerBuild
# This builds and saves your image as tarball to target/jib-image.tar
$ mvn compile jib:buildTar
# you can load tar Image into docker with
docker load --input target/jib-image.tar
$
The only configuration we needed is to provide was <to><image>, which is the name of our Docker image. We can now use that plugin to create a Docker image that will be available on our local Docker daemon.
To build the Docker image, run the command, without maven dependency in pom.xml
mvn compile com.google.cloud.tools:jib-maven-plugin:2.3.0:dockerBuild
**Using DockerHub**
1.add registry credentials to the Maven Maven\conf\settings.xml file
<server>
<id>registry.hub.docker.com</id>
<username>smlcodes</username>
<password>passw0rd@</password>
</server>
2.Update pom.xml
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>2.6.0</version>
<configuration>
<from>
<image>gcr.io/distroless/java:8</image>
</from>
<to>
<image>smlcodes/docker-employeeservices:${project.version}</image>
</to>
</configuration>
</plugin>
-
Typically, you don’t need to provide <from> as by default, as it uses a distro-less Java 8 image. However, I have used Java 11, so I have explicitly mentioned that here. Moreover, depending on your use case, you may want to use a different base image
-
<image> This refers to the target image that will pushed to the container registry.
To Build & Publish docker image
mvn compile jib:build
Check Docker HUB online
I have used a Docker registry, but you can use any cloud provider’s (ECS, GCR, ACR) container registry. For other options available for use with the plugin, you can refer to the documentation.
3.Using the dockerfile-maven-plugin
Add your Dockerfile into your root directory alongside your pom.xml.
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
4.Jenkins Configuration
Downoad & Start Jenkins from here
cd C:\Docker\Jenkins
java -jar jenkins.war --httpPort=2222
echo "Jenkins Started . . . "
Login to Jenkins
admin/admin
5.Docker Image PUSH to JFrog Artifactory
Go to JFrog Website: https://jfrog.com/start-free create an account, login
On your system cmdline
docker login satyajohnny.jfrog.io
From next time onwards it won’t ask for Credentials
Go to Administration > Add Repository > Local > Select : Docker > Name : jenkins-docker-images
To Integrate with Jenkins, follow http://satyacodes.ml/jenkins/jfrog-integration
pipeline {
agent
tools {
//Java, Maven Configured in Globol tools configuration. and here the names we provided for configuration
'Java8'
'Maven'
}
stages {
stage ('Clone') {
steps {
//github-creds are github login creds, configured in Credencial Manager
git branch: 'main',
credentialsId: 'github-creds',
url: 'https://github.com/smlcodes/Docker-SpringBoot-EmployeeServices.git'
}
}
/*
Artifactory-1 - similarly like Globol tool configuration.
But we are configured manually, passed as ServerID
rtMavenResolver - To get Jars of Pom.xml
rtMavenDeployer - To Deploy Newly created jars on Build
*/
stage ('Artifactory Configuration') {
steps {
rtServer (
: 'Artifactory-1',
url: "https://satyajohnny.jfrog.io/artifactory",
credentialsId: "jfrog-creds"
)
rtMavenResolver (
: 'maven-resolver',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release',
snapshotRepo: 'libs-release-libs-snapshot'
)
rtMavenDeployer (
: 'maven-deployer',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release-local',
snapshotRepo: 'libs-release-libs-snapshot-local',
threads: 6
)
}
}
stage('Build Maven Project') {
steps {
rtMavenRun (
tool: 'Maven',
pom: 'pom.xml',
goals: '-X clean install',
deployerId: "maven-deployer",
resolverId: "maven-resolver"
)
}
}
}
}
After Running this New jars added to JFrog Artifactory
5.Docker Image Push to Docker HUB
First, create a environment to save docker image information’s.
dockerImage = ''
Change the build stage to save build information in environment.
dockerImage = docker.build registry + ":$BUILD_NUMBER"
pipeline {
environment {
registry = "smlcodes/docker-employeeservices"
registryCredential = 'docker-creds'
dockerImage = ''
}
agent
tools {
'Java8'
'Maven'
}
stages {
stage('Clone') {
steps {
git branch: 'main',
credentialsId: 'github-creds',
url: 'https://github.com/smlcodes/docker-springboot-employeeservices.git'
}
}
/*
Artifactory-1 - similarly like Globol tool configuration.
But we are configured manually, passed as ServerID
rtMavenResolver - To get Jars of Pom.xml
rtMavenDeployer - To Deploy Newly created jars on Build
*/
stage('Artifactory Configuration') {
steps {
rtServer(
: 'Artifactory-1',
url: "https://satyajohnny.jfrog.io/artifactory",
credentialsId: "jfrog-creds"
)
rtMavenResolver(
: 'maven-resolver',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release',
snapshotRepo: 'libs-release-libs-snapshot'
)
rtMavenDeployer(
: 'maven-deployer',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release-local',
snapshotRepo: 'libs-release-libs-snapshot-local',
threads: 6
)
}
}
stage('Build Maven Project') {
steps {
rtMavenRun(
tool: 'Maven',
pom: 'pom.xml',
goals: '-U clean install',
deployerId: "maven-deployer",
resolverId: "maven-resolver"
)
}
}
stage('Build Docker Image') {
steps {
"\n \n \n \n ***************************************"
" Build Docker Image"
"***************************************\n \n \n"
script {
docker.build("satyajohnny.jfrog.io/" + "docker-employeeservices:2.0.${env.BUILD_NUMBER}")
dockerImage = docker.build registry + ":$BUILD_NUMBER"
}
}
}
stage('Push Image to DockerHub') {
steps {
script {
"\n \n \n \n ***************************************"
" Push Image to DockerHub"
"*********************************\n \n \n"
docker.withRegistry('', registryCredential) {
dockerImage.push()
}
}
}
}
} //Stages End
} //Pipeline End
5.Docker Image Push to Azure Container Registry
To Quick start Azure Container Registry follow
https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal
Select Create a resource > Containers > Container Registry.
Go to Access Keys in Container Registry and enable the admin user, then use the autogenerated credentials to login via Docker
Configure these credentials in Jenkins with name 'azurecontainer-creds'
pipeline {
environment {
dockerregistry = "smlcodes/docker-employeeservices"
dockercreds = 'docker-creds'
dockerHubImage = ''
//Go to Access Keys in Container Registry and enable the admin user, then use the autogenerated credentials to login via Docker
azureregistry = "satyajohnnycontainer.azurecr.io"
azurecreds = 'azurecontainer-creds'
azureImage = ''
}
agent
tools {
'Java8'
'Maven'
}
stages {
stage('Clone') {
steps {
git branch: 'main',
credentialsId: 'github-creds',
url: 'https://github.com/smlcodes/docker-springboot-employeeservices.git'
}
}
stage('Artifactory Configuration') {
steps {
rtServer(
: 'Artifactory-1',
url: "https://satyajohnny.jfrog.io/artifactory",
credentialsId: "jfrog-creds"
)
rtMavenResolver(
: 'maven-resolver',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release',
snapshotRepo: 'libs-release-libs-snapshot'
)
rtMavenDeployer(
: 'maven-deployer',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release-local',
snapshotRepo: 'libs-release-libs-snapshot-local',
threads: 6
)
}
}
stage('Build Maven Project') {
steps {
rtMavenRun(
tool: 'Maven',
pom: 'pom.xml',
goals: '-U clean install',
deployerId: "maven-deployer",
resolverId: "maven-resolver"
)
}
}
stage('Build Docker Image') {
steps {
script {
azureImage = docker.build("satyajohnnycontainer.azurecr.io/" + "docker-employeeservices:2.0.${env.BUILD_NUMBER}")
}
}
}
stage('Push Image to Azure Container Registry') {
steps {
script {
docker.withRegistry('https://satyajohnnycontainer.azurecr.io', azurecreds) {
azureImage.push()
}
}
}
}
stage ('Publish Build Info') {
steps {
rtPublishBuildInfo (
serverId: "Artifactory-1"
)
}
}
} //Stages End
} //Pipeline End
Now go to Azure Container Registry > Services > Repository , see Docker Image published
#Login
az acr login --name satyajohnnycontainer
#List images in registry
az acr repository list --name satyajohnnycontainer --output table
6.AKS – Azure Kubernetes Service
AKS is a managed Kubernetes service that lets you quickly deploy and manage clusters. We will see how to create AKS cluster in Azure cloud using Azure CLI.
Pre-requisites:
-
Azure CLI is installed on your local machine.
-
Account setup in Azure.
Create Azure Container Registry
#Login to Azure
az login
#Create a resource group first.
az group create --name myResourceGroup --location eastus
Run the below command to create your own private container registry using Azure Container Registry
az acr create --resource-group myResourceGroup --name satyajohnnycontainer --sku Standard --location eastus
Create AKS cluster with 2 worker nodes
az aks create --resource-group myResourceGroup --name --node-count 2 --enable-addons monitoring --generate-ssh-keys
C:\Windows\system32> --resource-group myResourceGroup --name --node-count 2 --enable-addons monitoring --generate-ssh-keys
SSH key files 'C:\Users\kavetis\.ssh\id_rsa' and 'C:\Users\kavetis\.ssh\id_rsa.pub' have been generated under ~/.ssh to allow SSH access to the VM. If using machines without permanent storage like Azure Cloud Shell without an attached file share, back up your keys to a safe location
Resource provider 'Microsoft.OperationalInsights' used by this operation is not registered. We are registering for you.
Once Above cmd run completes, we can see many no. of services are created which are required by AKS.
We can see 2 Nodes inside AKS Cluster
Connect to the cluster
az aks get-credentials --resource-group myResourceGroup --name --overwrite-existing
To verify the connection to your cluster, use the kubectl get command to return a list of the cluster nodes.
kubectl get nodes
# List all deployments in a specific namespace
kubectl get deployments --all-namespaces=true
Update Jenkins Pipeline
pipeline {
agent
environment {
registry = "smlcodes/docker-employeeservices"
registryCredential = 'docker-creds'
dockerImage = ''
}
stages {
stage('checkout') {
steps {
git branch: 'main',
credentialsId: 'github-creds',
url: 'https://github.com/smlcodes/docker-springboot-employeeservices.git'
}
}
stage('Artifactory Configuration') {
steps {
rtServer(
: 'Artifactory-1',
url: "https://satyajohnny.jfrog.io/artifactory",
credentialsId: "jfrog-creds"
)
rtMavenResolver(
: 'maven-resolver',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release',
snapshotRepo: 'libs-release-libs-snapshot'
)
rtMavenDeployer(
: 'maven-deployer',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release-local',
snapshotRepo: 'libs-release-libs-snapshot-local',
threads: 6
)
}
}
stage('Build Maven Project') {
steps {
rtMavenRun(
tool: 'Maven',
pom: 'pom.xml',
goals: '-U clean install',
deployerId: "maven-deployer",
resolverId: "maven-resolver"
)
}
}
stage('Build Docker image') {
steps {
script {
dockerImage = docker.build registry + ":$BUILD_NUMBER"
}
}
}
// Uploading Docker images into Docker Hub
stage('Upload Image to Docker HUB') {
steps {
script {
docker.withRegistry('', registryCredential) {
dockerImage.push()
dockerImage.push('latest')
}
}
}
}
stage('Remove Unused docker image') {
steps {
"docker rmi $registry:$BUILD_NUMBER"
}
}
stage('K8S Deploy') {
steps {
script {
kubernetesDeploy(
configs: 'Kubernetes.yaml',
kubeconfigId: 'aks-creds',
enableConfigSubstitution: true
)
}
}
}
}
}
always {
// remove built docker image and prune system
print 'Cleaning up the Docker system.'
'docker system prune -f'
}
Kubernetes.yaml
– For Docker HUB
apiVersion: apps/v1
kind: Deployment
metadata:
name: docker-employeeservices-manual
spec:
replicas: 2
selector:
matchLabels:
app: docker-employeeservices-manual
template:
metadata:
labels:
app: docker-employeeservices-manual
spec:
containers:
- name: docker-employeeservices-manual
image: smlcodes/docker-employeeservices
# image: wingtiptoysregistry.azurecr.io/gs-spring-boot-docker:latest
---
apiVersion: v1
kind: Service
metadata:
name: docker-employeeservices-manual-service
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: docker-employeeservices-manual
Build Pipeline
Build Is Success, But if you see AKS Cluster it is unable to Pull the Image
Here we can see that AKS is not able to pull image from ACR. Let’s describe the pod and look for more information.
kubectl get pods
docker-employeeservices-acs-86b4879d-7h5x5 0/1 ImagePullBackOff 0 16h
docker-employeeservices-acs-86b4879d-b4zc2 0/1 ImagePullBackOff 0 16h
kubectl describe pods docker-employeeservices-acs-86b4879d-7h5x5
Here the Problem is ACS & AKS Integration. For that we need to run below command
az aks update -n myAKSCluster -g myResourceGroup --attach-acr <acr-name>
az aks update -n satyajohnnyAKSCluster -g myResourceGroup --attach-acr satyajohnnycontainer
When we attach ACR to AKS, it actually grants the ‘acrpull’ role assignment to ACR specified by name or resource ID. Check Integration Status
az aks check-acr --name satyajohnnyAKSCluster --resource-group myResourceGroup
\--acr satyajohnnycontainer.azurecr.io
Re-Run Pipeline Again.
7. Verify deployments to K8S
kubectl get pods
kubectl get deployments
kubectl get services
Here External-IP is the Public IP Where we can access Our Application. You can also get from UI, Go to AKS Portal in below Image
Check Pods are Running
open public IP without port
8. Final Working Pipeline Code
// Dockerfile
# Image based on JAVA Container
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} Docker-EmployeeServices-2.0.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/Docker-EmployeeServices-2.0.jar"]
// Jenkinsfile
pipeline {
environment {
dockerregistry = "smlcodes/docker-employeeservices"
dockercreds = 'docker-creds'
dockerHubImage = ''
//Go to Access Keys in Container Registry and enable the admin user, then use the autogenerated credentials to login via Docker
azureregistry = "satyajohnnycontainer.azurecr.io"
azurecreds = 'azurecontainer-creds'
azureImage = ''
}
agent
tools {
//Java, Maven Configured in Globol tools configuration. and here the names we provided for configuration
'Java8'
'Maven'
}
stages {
stage('Clone') {
steps {
//github-creds are github login creds, configured in Credencial Manager
git branch: 'main',
credentialsId: 'github-creds',
url: 'https://github.com/smlcodes/docker-springboot-employeeservices.git'
}
}
/*
Artifactory-1 - similarly like Globol tool configuration.
But we are configured manually, passed as ServerID
rtMavenResolver - To get Jars of Pom.xml
rtMavenDeployer - To Deploy Newly created jars on Build
*/
stage('Artifactory Configuration') {
steps {
rtServer(
: 'Artifactory-1',
url: "https://satyajohnny.jfrog.io/artifactory",
credentialsId: "jfrog-creds"
)
rtMavenResolver(
: 'maven-resolver',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release',
snapshotRepo: 'libs-release-libs-snapshot'
)
rtMavenDeployer(
: 'maven-deployer',
serverId: 'Artifactory-1',
releaseRepo: 'libs-release-libs-release-local',
snapshotRepo: 'libs-release-libs-snapshot-local',
threads: 6
)
}
}
stage('Build Maven Project') {
steps {
rtMavenRun(
tool: 'Maven',
pom: 'pom.xml',
goals: '-U clean install',
deployerId: "maven-deployer",
resolverId: "maven-resolver"
)
}
}
stage('Build Docker Image') {
steps {
"\n \n \n \n ******************************************************************************"
" Build Docker Image"
"******************************************************************************\n \n \n"
script {
//docker.build("satyajohnny.jfrog.io/" + "docker-employeeservices:2.0.${env.BUILD_NUMBER}")
dockerHubImage = docker.build dockerregistry + ":$BUILD_NUMBER"
azureImage = docker.build("satyajohnnycontainer.azurecr.io/" + "docker-employeeservices:${env.BUILD_NUMBER}")
}
}
}
stage('Push Image to Azure Container Registry') {
steps {
script {
"\n \n \n \n ******************************************************************************"
" Push Image to Azure Container Registry"
"******************************************************************************\n \n \n"
docker.withRegistry('https://satyajohnnycontainer.azurecr.io', azurecreds) {
azureImage.push()
azureImage.push('latest')
}
}
}
}
// Uploading Docker images into Docker Hub
stage('Push Image to Docker HUB') {
steps {
script {
docker.withRegistry('', dockercreds) {
dockerHubImage.push()
dockerHubImage.push('latest')
}
}
}
}
stage('Remove Unused docker image') {
steps {
"docker rmi $dockerregistry:$BUILD_NUMBER"
}
}
// Deploy To Azure Kubernetes Cluster
stage('K8S Deploy') {
steps {
script {
kubernetesDeploy(
configs: 'Kubernetes.yaml',
kubeconfigId: 'aks-creds',
enableConfigSubstitution: true
)
}
}
}
/*
Push Image to Artfifactory Not Woking
serverId : Configred Artfifactory Server Name
host : docker daemon host where Docker currenlty running system/localhost
targetRepo : Repo name in Artfifactory
*/
/*
stage ('Push Image to Artifactory') {
steps {
echo "\n \n \n \n ******************************************************************************"
echo " Not Working on LocalHost : Push Docker Image to Artifactory"
echo "******************************************************************************\n \n \n"
rtDockerPush(
serverId: "Artifactory-1",
image: "satyajohnny.jfrog.io/" + "docker-employeeservices:2.0.${env.BUILD_NUMBER}",
host: 'tcp://thedockerhost:2375/',
targetRepo: 'jenkins-docker-images',
properties: 'project-name=docker-employeeservices:2.0;status=stable'
)
}
}
*/
/*
stage('Push Image to DockerHub') {
steps {
script {
echo "\n \n \n \n ******************************************************************************"
echo " Working - Push Image to DockerHub"
echo "******************************************************************************\n \n \n"
docker.withRegistry('', dockercreds) {
dockerHubImage.push()
}
}
}
}
*/
} //Stages End
} //Pipeline End
always {
// remove built docker image and prune system
print 'Cleaning up the Docker system.'
'docker system prune -f'
}
Kubernetes.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: docker-employeeservices-acs
spec:
replicas: 2
selector:
matchLabels:
app: docker-employeeservices-acs
template:
metadata:
labels:
app: docker-employeeservices-acs
spec:
containers:
- name: docker-employeeservices-acs
image: satyajohnnycontainer.azurecr.io/docker-employeeservices:latest
#image: smlcodes/docker-employeeservices
---
apiVersion: v1
kind: Service
metadata:
name: docker-employeeservices-acs-service
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: docker-employeeservices-acs
9. Delete Cluster
To avoid Azure charges, you should clean up unneeded resources. When the cluster is no longer needed, use the az group delete command to remove the resource group, container service, and all related resources.
az group delete --name myResourceGroup --yes --no-wait
Ref.
-
https://www.coachdevops.com/2020/06/deploy-python-app-into-kubernetes.html
-
https://medium.com/swlh/build-a-docker-image-using-maven-and-spring-boot-58147045a400
-
https://medium.com/@gustavo.guss/jenkins-building-docker-image-and-sending-to-registry-64b84ea45ee9
-
https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal
-
https://jfrog.com/blog/ci-cd-side-by-side-jenkins-and-jfrog-pipelines/
-
https://www.jfrog.com/confluence/display/JFROG/Declarative+Pipeline+Syntax
-
https://github.com/talitz/spring-petclinic-ci-cd-k8s-example/blob/master/Jenkinsfile
-
https://dzone.com/articles/building-docker-images-to-docker-hub-using-jenkins
-
https://stackoverflow.com/questions/58203820/jenkins-with-jfrog-artifactory-push-docker-images
-
https://github.com/dumindarw/reactive-eventservice/blob/master/Jenkinsfile
-
https://ystatit.medium.com/azure-configure-acr-integration-for-existing-aks-cluster-c551c678fede
-
https://github.com/dumindarw/reactive-eventservice/blob/master/Jenkinsfile