How to Build a Scalable Pre-Production Environment for Microservices in 2025 with Jenkins & k3s

Creating a reliable pre-production environment for a modern microservices architecture can be challenging—especially when you need rapid feedback loops, predictable deployments, and minimal infrastructure overhead. In this guide, you’ll learn step by step how to spin up a lightweight Kubernetes cluster with k3s, host your own Docker registry, and configure Jenkins declarative pipelines to automate build, push, and deploy for every service.

By following this post, you’ll be production-ready in under an hour, and you can kick-start your own projects using our open-source template on GitHub:
👉 Template Repo: https://github.com/JoaquinRuiz/microservices-jenkins-k8s-template


Why a Dedicated Pre-Production Environment Matters

  • Catch regressions early. Testing against a real Kubernetes cluster exposes issues that local Docker Compose or mocks can miss.
  • Mirror production. A k3s single-node cluster with the same CNI and networking model minimizes surprises when you go live.
  • Automate everything. From commit to deployment, you want zero manual steps.

1. Deploy k3s: Your Lightweight Kubernetes

k3s has become the de facto choice in 2025 for edge, CI, and lightweight clusters. To install:

curl -sfL https://get.k3s.io | sh -

This installer bundles:

  • Control plane (API server, etcd)
  • Container runtime (containerd)
  • Flannel CNI
  • kube-proxy and local path provisioner

Verify with:

kubectl get nodes
kubectl get pods -n kube-system

Your node should be Ready and core pods Running.


2. Stand Up a Local Registry

A private registry is essential for your CI pipeline to push images and for k3s to pull them. The simplest approach:

docker run -d \
--restart=always \
--name registry \
-p 5000:5000 \
registry:2

Test:

curl -I http://localhost:5000/v2/
# Expect HTTP/200 or HTTP/401

Mark It Insecure for Docker & containerd

Docker CLI (for Jenkins):

# /etc/docker/daemon.json
{
"insecure-registries": ["localhost:5000"]
}
sudo systemctl restart docker

containerd (used by k3s):

# /etc/rancher/k3s/registries.yaml
mirrors:
"localhost:5000":
endpoint:
- "http://localhost:5000"
configs:
"localhost:5000":
tls:
insecure_skip_verify: true
sudo systemctl restart k3s

Now both Docker and k3s can use localhost:5000 over plain HTTP.


3. Clone the GitHub Template

We’ve curated all best practices into a ready-to-use GitHub template. Clone it:

git clone https://github.com/JoaquinRuiz/microservices-jenkins-k8s-template.git
cd microservices-jenkins-k8s-template

Inside you’ll find:

  • Jenkinsfile: Declarative CI/CD with stages for build, push, and deploy.
  • Dockerfile: Example Node.js service container.
  • k8s/: Per-service Deployment & Service manifests.

4. Configure Jenkins

  1. Install plugins:
    • Git & Multibranch Pipeline
    • Docker Pipeline
    • Credentials Binding
  2. Global Tools:
    • JDK (e.g. Java 21)
    • Docker installation
  3. Credentials:
    • SSH key for GitHub
    • Secret file for k3s kubeconfig (k3s-kubeconfig)

Create a Multibranch Pipeline in Jenkins, pointing at your cloned repository. Jenkins will auto-discover branches with a Jenkinsfile.


5. Understand the Jenkinsfile

pipeline {
agent any
tools { jdk 'Java 21'; dockerTool 'docker' }
environment {
REGISTRY = 'localhost:5000'
IMAGE = "${REGISTRY}/${JOB_NAME}:${BUILD_NUMBER}"
}
stages {
stage('Checkout') { steps { checkout scm } }
stage('Build') {
steps { sh "docker build -t ${IMAGE} ." }
}
stage('Push') {
steps { sh "docker push ${IMAGE}" }
}
stage('Deploy') {
steps {
withCredentials([file(credentialsId:'k3s-kubeconfig',variable:'KUBECONFIG')]) {
sh "kubectl apply -f k8s/${JOB_NAME}/service.yaml"
sh "kubectl apply -f k8s/${JOB_NAME}/deployment.yaml"
sh "kubectl set image deployment/${JOB_NAME} ${JOB_NAME}=${IMAGE} --record"
}
}
}
}
post {
success { echo "✅ Deployed ${IMAGE}" }
failure { echo "❌ Pipeline failed" }
}
}
  • ${JOB_NAME} dynamically maps to each microservice folder.
  • set image … --record triggers a rolling update with history.

6. Kubernetes Manifests

Each service gets its own folder under k8s/. Example structure:

k8s/
└── my-service/
├── deployment.yaml
└── service.yaml

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
spec:
replicas: 2
selector:
matchLabels: { app: my-service }
template:
metadata:
labels: { app: my-service }
spec:
containers:
- name: my-service
image: placeholder
ports:
- containerPort: 3000
readinessProbe:
httpGet: { path: "/", port: 3000 }
initialDelaySeconds: 5
periodSeconds: 10

service.yaml

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector: { app: my-service }
ports:
- port: 80; targetPort: 3000; nodePort: 30XXX

7. Test & Troubleshoot

  • Pods & Services: bashCopiarEditarkubectl get pods -l app=my-service kubectl get svc my-service kubectl get endpoints my-service
  • NodePort access: bashCopiarEditarcurl -I http://<NODE_IP>:<NODE_PORT>/
  • Debug image pull: bashCopiarEditarsudo crictl pull localhost:5000/my-service:latest
  • Inspect registry: bashCopiarEditarcurl -s http://localhost:5000/v2/my-service/tags/list | jq .

Get Started Now

🚀 Clone the template and accelerate your microservices pre-production pipeline today:
https://github.com/JoaquinRuiz/microservices-jenkins-k8s-template

Leverage the power of k3s, Jenkins, and a local registry to deliver rock-solid deployments—every commit, every time.

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x