This repository demonstrates continuous deployment using GitHub Actions to automatically build, test, and deploy a Node.js application to a Minikube cluster. The lab showcases CI/CD pipeline automation, containerization, and Kubernetes orchestration integrated with GitHub's native workflow system.
This lab implements a complete CI/CD pipeline that triggers on every code push, building a Docker image from a simple Node.js Express application, deploying it to a local Minikube Kubernetes cluster, and validating the deployment—all automated through GitHub Actions workflows.
- Automated CI/CD Pipeline: GitHub Actions workflow triggered on push events
- Container Building: Docker image creation within the GitHub Actions runner
- Kubernetes Deployment: Automated deployment to Minikube cluster
- Service Validation: Post-deployment testing and service URL verification
- Infrastructure as Code: Kubernetes manifests for declarative deployment
- Development Workflow: Testing Kubernetes deployments locally before production
- Lab Objectives
- Architecture
- Application Components
- CI/CD Pipeline
- Implementation Details
- Challenges and Solutions
- Key Learnings
- Prerequisites
- Getting Started
- Repository Structure
- Workflow Execution
- Testing
- Credits
- Automate deployment using GitHub Actions workflows
- Integrate Minikube with CI/CD pipeline for local Kubernetes testing
- Build and deploy containerized Node.js application
- Validate deployments automatically through workflow steps
- Implement best practices for CI/CD pipeline design
- GitHub Actions workflow syntax and job configuration
- Docker image building in CI/CD environments
- Minikube integration for testing Kubernetes deployments
- Automated testing and validation strategies
- Debugging CI/CD pipeline failures
- YAML syntax and Kubernetes manifest structure
Git Push → GitHub Repository
↓
GitHub Actions Triggered
↓
1. Checkout Code
↓
2. Start Minikube Cluster
↓
3. Verify Cluster (kubectl get pods)
↓
4. Build Docker Image
↓
5. Deploy to Minikube (kubectl apply)
↓
6. Wait for Service Initialization
↓
7. Validate Deployment
↓
8. Test Service URLs
↓
Pipeline Success ✓
GitHub Actions Runner
↓
[Minikube Cluster]
↓
[NodePort Service:80]
↓
[Deployment: nodejs-app]
↓
[Pod: Node.js Express Container:3000]
↓
Response: "Hello World"
server.js - Simple Express web server:
- Listens on port 3000
- Serves "Hello World" response on root path (
/) - Designed for easy testing and validation
- Lightweight and fast startup time
package.json - Application metadata and dependencies:
- Express framework v4.16.1
- Start script for application execution
- Minimal dependencies for quick builds
Dockerfile - Multi-step container build:
- Base Image: Node.js 14 official image
- Working Directory:
/usr/src/app - Dependency Installation: npm install with package.json
- Application Copy: Source code copied to container
- Port Exposure: Port 3000 exposed for HTTP traffic
- Startup Command: Executes
node server.js
Key Dockerfile Features:
- Layer caching optimization (package.json copied before source)
- Explicit Express installation for reliability
- Non-root user execution (inherited from node image)
- Clear port documentation
File: .github/workflows/deploy-to-minikube-github-actions.yaml
Trigger: Push to any branch
Job Configuration:
- Runner: ubuntu-latest (GitHub-hosted)
- Name: Build Node.js Docker Image and deploy to minikube
- uses: actions/checkout@v2Clones repository code into the runner workspace.
- name: Start minikube
uses: medyagh/setup-minikube@masterInitializes a local Kubernetes cluster using the setup-minikube action.
Why This Works:
- Installs kubectl, minikube, and required dependencies
- Configures Docker as container runtime
- Starts single-node cluster suitable for testing
- name: Try the cluster!
run: kubectl get pods -AValidates Kubernetes cluster is operational by listing all pods across namespaces.
- name: Build image
run: |
export SHELL=/bin/bash
eval $(minikube -p minikube docker-env)
docker build -f ./Dockerfile -t devopshint/node-app:latest .
echo -n "verifying images:"
docker imagesKey Operations:
- Sets shell environment for consistency
- Configures Docker to use Minikube's Docker daemon (crucial!)
- Builds image with latest tag
- Verifies image creation
Why eval $(minikube docker-env)?
This connects the Docker client to Minikube's internal Docker daemon, meaning images built are directly available to the Kubernetes cluster without pushing to a registry.
- name: Deploy to minikube
run: kubectl apply -f k8s-node-app.yamlApplies Kubernetes manifests, creating Deployment and Service resources.
- name: Wait for deployment
run: kubectl wait --for=condition=available --timeout=60s deployment/nodejs-appThis step was added during troubleshooting to ensure the service is ready before testing.
- name: Test service URLs
run: |
minikube service list
minikube service nodejs-app --urlLists all services and retrieves the NodePort URL for the deployed application.
k8s-node-app.yaml contains two resources:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-app
namespace: default
labels:
app: nodejs-app
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-app
template:
metadata:
labels:
app: nodejs-app
spec:
containers:
- name: nodejs-app
image: "devopshint/node-app:latest"
ports:
- containerPort: 3000Configuration Details:
- Single replica for testing purposes
- Uses locally built Docker image
- Container listens on port 3000
- Label selector enables Service discovery
apiVersion: v1
kind: Service
metadata:
name: nodejs-app
namespace: default
spec:
selector:
app: nodejs-app
type: NodePort
ports:
- name: http
port: 80
targetPort: 3000Service Configuration:
- Type: NodePort (exposes service outside cluster)
- Port: 80 (external service port)
- TargetPort: 3000 (container port)
- Selector: Routes traffic to Pods with
app: nodejs-applabel
Why NodePort?
NodePort makes the service accessible via <NodeIP>:<NodePort>, perfect for testing with Minikube's service command.
Problem: After deployment, attempting to access the service immediately resulted in connection errors. The workflow failed at the testing step because the service hadn't fully initialized.
Root Cause: Kubernetes resources take time to become available:
- Deployment needs to create Pods
- Pods need to pull image (if not cached)
- Container needs to start
- Service endpoint needs to register
Solution: Added explicit wait step to the workflow:
- name: Wait for deployment
run: kubectl wait --for=condition=available --timeout=60s deployment/nodejs-appResult: Ensured deployment was fully ready before running tests, eliminating race conditions.
Problem: GitHub Actions workflow failed with YAML parsing error. The manifest wasn't being accepted by kubectl.
Error Message:
error: error parsing k8s-node-app.yaml: error converting YAML to JSON
Root Cause: Incorrect indentation in the Kubernetes manifest file. YAML is whitespace-sensitive, and even minor spacing issues cause parsing failures.
Solution:
- Validated YAML syntax using online YAML validator
- Corrected indentation to use consistent 2-space spacing
- Ensured proper alignment of nested elements
- Verified manifest structure against Kubernetes documentation
Best Practice: Always validate YAML files before committing:
kubectl apply -f k8s-node-app.yaml --dry-run=clientProblem: Kubernetes deployment failed with ImagePullBackOff error, unable to find the specified Docker image.
Error Message:
Failed to pull image "devopshint/node-app:latest": rpc error: code = Unknown
Root Cause: Multiple possible causes identified:
- Image wasn't built before deployment attempted to use it
- Docker image not available in Minikube's Docker daemon
- Incorrect image tag or registry path
Solution:
- Verified build step completed successfully with
docker imagescommand - Ensured
eval $(minikube docker-env)was executed before building - Confirmed image name matched exactly in manifest and build command
- Added image verification in the build step output
Key Insight: Using Minikube's Docker daemon (minikube docker-env) is crucial. Without this, images are built in the host Docker daemon and unavailable to Kubernetes.
Problem: Environment variables not persisting across workflow steps, causing Docker environment configuration to fail.
Solution: Used proper shell configuration:
run: |
export SHELL=/bin/bash
eval $(minikube -p minikube docker-env)Setting SHELL explicitly ensures consistent behavior across different runner environments.
- Explicit waiting prevents race conditions in deployment pipelines
- Validation steps after each major operation catch issues early
- Docker daemon configuration is critical when using Minikube in CI/CD
- Idempotent operations using
kubectl applyenable safe re-runs
- Actions marketplace provides pre-built integrations (setup-minikube)
- Multi-line commands require pipe (
|) syntax - Each step runs in fresh shell unless configured otherwise
- Output visibility helps debugging with echo statements
- Declarative manifests enable version control and repeatability
- NodePort services suitable for development and testing
- Label selectors create loose coupling between resources
- Health checks should be added for production-ready deployments
- Layer caching improves build performance
- Multi-stage builds reduce final image size (future enhancement)
- Specific version tags ensure reproducible builds
- Image verification after building catches build failures
- Git: Version control
- GitHub Account: Repository hosting and Actions access
- Basic knowledge: Node.js, Docker, Kubernetes, YAML
- Docker: Container runtime
- Minikube: Local Kubernetes cluster
- kubectl: Kubernetes CLI
- Node.js 14: Application runtime
-
Fork/Clone Repository:
git clone <repository-url> cd <repository-name>
-
Review Application Code:
# Check server.js cat server.js # Review Dockerfile cat Dockerfile # Examine Kubernetes manifests cat k8s-node-app.yaml
-
Examine GitHub Actions Workflow:
cat .github/workflows/deploy-to-minikube-github-actions.yaml
-
Push Changes to Trigger Workflow:
git add . git commit -m "Trigger CI/CD pipeline" git push origin main
-
Monitor Workflow:
- Navigate to repository on GitHub
- Click "Actions" tab
- View running workflow
- Check each step's logs
# Build Docker image locally
docker build -t devopshint/node-app:latest .
# Start Minikube
minikube start
# Configure Docker to use Minikube's daemon
eval $(minikube docker-env)
# Rebuild image in Minikube's Docker
docker build -t devopshint/node-app:latest .
# Deploy to Minikube
kubectl apply -f k8s-node-app.yaml
# Wait for deployment
kubectl wait --for=condition=available --timeout=60s deployment/nodejs-app
# Get service URL
minikube service nodejs-app --url
# Test application
curl $(minikube service nodejs-app --url).
├── .github/
│ └── workflows/
│ └── deploy-to-minikube-github-actions.yaml # CI/CD workflow
│
├── Dockerfile # Container image definition
├── package.json # Node.js dependencies
├── server.js # Express application
├── k8s-node-app.yaml # Kubernetes manifests
└── README.md # This file
When you push code, GitHub Actions executes the workflow:
Expected Output:
✓ Checkout code
✓ Start Minikube
✓ Try the cluster
✓ Build image
✓ Deploy to Minikube
✓ Wait for deployment
✓ Test service URLs
GitHub Actions Interface:
- Navigate to repository → Actions tab
- Click on latest workflow run
- Expand each step to view logs
- Verify "Test service URLs" shows Minikube service URL
Key Indicators of Success:
- All steps show green checkmarks
- Docker image appears in build step output
- Deployment becomes available within timeout
- Service URL is successfully retrieved
The workflow automatically tests deployment by:
- Verifying cluster connectivity
- Confirming image build success
- Checking deployment availability
- Retrieving service URL
If running locally with Minikube:
# Get service URL
SERVICE_URL=$(minikube service nodejs-app --url)
# Test with curl
curl $SERVICE_URL
# Expected output: Hello World
# Or open in browser
minikube service nodejs-app# Check deployment status
kubectl get deployments
# Check pod status
kubectl get pods
# Check service
kubectl get services
# View pod logs
kubectl logs -l app=nodejs-app
# Describe deployment for details
kubectl describe deployment nodejs-appThis repository documents personal learning progress through a DevOps bootcamp. While it's primarily for educational purposes, suggestions and improvements are welcome!
-
Report Issues: Found a bug or error in scripts?
- Open an issue describing the problem
- Include script name and error message
- Provide steps to reproduce
-
Suggest Improvements: Have ideas for better implementations?
- Fork the repository
- Create a feature branch
- Submit pull request with clear description
-
Share Knowledge: Learned something new?
- Add comments or documentation
- Create additional practice exercises
- Write tutorials or guides
This project is created for educational purposes as part of a DevOps bootcamp internship.
Educational Use: Feel free to use these scripts and documentation for learning purposes.
Attribution: If you use or reference this work, please provide attribution to the original author.
No Warranty: These scripts are provided "as is" without warranty of any kind. Use at your own risk, especially in production environments.