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
- Install plugins:
- Git & Multibranch Pipeline
- Docker Pipeline
- Credentials Binding
- Global Tools:
- JDK (e.g. Java 21)
- Docker installation
- 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: bashCopiarEditar
kubectl get pods -l app=my-service kubectl get svc my-service kubectl get endpoints my-service
- NodePort access: bashCopiarEditar
curl -I http://<NODE_IP>:<NODE_PORT>/
- Debug image pull: bashCopiarEditar
sudo crictl pull localhost:5000/my-service:latest
- Inspect registry: bashCopiarEditar
curl -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.