A comprehensive Java web server application with enterprise-grade CI/CD pipeline featuring environment separation, approval gates, and automated deployments.
This project demonstrates an enterprise-grade DevOps pipeline that:
- Builds a simple Java HTTP server
- Containerizes it using Docker
- Deploys across multiple environments (Development → Staging → Production)
- Includes comprehensive testing, health checks, and approval gates
- Features automated notifications and rollback capabilities
Github-Action-POC/
├── .github/workflows/
│ ├── ci-build.yml # CI: Build, test, publish images
│ ├── pr-validation.yml # PR: Validation workflow for pull requests
│ ├── deploy-dev.yml # Deploy to development environment
│ ├── deploy-production.yml # Deploy to production (manual with approval)
│ └── docker-build-deploy.yml # Legacy workflow (deprecated)
├── k8s/
│ ├── namespace.yaml # Kubernetes namespace definition
│ ├── deployment.yaml # Kubernetes deployment configuration
│ ├── service.yaml # Kubernetes service (LoadBalancer)
│ └── ingress.yaml # Kubernetes ingress routing
├── Dockerfile # Docker container configuration
├── HelloWorldServer.java # Java web server application
├── DEPLOYMENT_GUIDE.md # Enterprise deployment guide
└── README.md # This documentation file
This file contains the main Java application that creates an HTTP server.
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;Lines 1-6: Import statements for Java's built-in HTTP server functionality:
HttpServer: Creates and manages the HTTP serverHttpHandler: Interface for handling HTTP requestsHttpExchange: Represents an HTTP request-response exchangeIOException: Exception handling for I/O operationsOutputStream: For writing response dataInetSocketAddress: For specifying server address and port
public class HelloWorldServer {Line 8: Class declaration for the main server class
public static void main(String[] args) throws IOException {Line 9: Main method entry point, declares IOException to handle network errors
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);Line 10: Creates HTTP server instance:
HttpServer.create(): Factory method to create servernew InetSocketAddress(8080): Binds server to port 8080 on all interfaces0: Uses default backlog (maximum number of incoming connections)
server.createContext("/", new HelloWorldHandler());Line 11: Maps the root path "/" to HelloWorldHandler class for request processing
server.setExecutor(null);Line 12: Uses default executor (creates thread pool automatically for handling requests)
System.out.println("Server started on http://localhost:8080");Line 13: Prints startup message to console for debugging
server.start();Line 14: Starts the HTTP server (non-blocking call)
static class HelloWorldHandler implements HttpHandler {Line 17: Inner static class that implements HttpHandler interface for request processing
@Override
public void handle(HttpExchange exchange) throws IOException {Line 19: Override method that handles all HTTP requests to the "/" context
String response = """
<!DOCTYPE html>
<html>
<head>
<title>Hello World Java App</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; }
h1 { color: #333; font-size: 3em; }
</style>
</head>
<body>
<h1>Hello! World</h1>
<p>Java application running in Docker with Ubuntu</p>
</body>
</html>
""";Lines 20-35: Text block (Java 15+ feature) containing HTML response:
- Complete HTML document with DOCTYPE declaration
- CSS styling for centered layout with Arial font
- Main heading "Hello! World" in large gray text
- Descriptive paragraph about the application environment
exchange.getResponseHeaders().set("Content-Type", "text/html");Line 37: Sets HTTP response header to indicate HTML content type
exchange.sendResponseHeaders(200, response.getBytes().length);Line 38: Sends HTTP response headers:
200: HTTP OK status coderesponse.getBytes().length: Content length in bytes
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();Lines 39-41: Writes response body and closes the output stream:
- Gets output stream from the exchange
- Writes HTML response as bytes
- Closes stream to complete the response
This file defines how to build the Docker container for the Java application.
# Use Ubuntu as base image
FROM ubuntu:22.04Lines 1-2:
- Comment explaining base image choice
- Uses Ubuntu 22.04 LTS as the foundation (stable, long-term support)
# Set environment variables
ENV DEBIAN_FRONTEND=noninteractiveLines 4-5:
- Comment describing environment setup
- Prevents interactive prompts during package installation
# Update package list and install OpenJDK 17
RUN apt-get update && \
apt-get install -y openjdk-17-jdk && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*Lines 7-11: Multi-line RUN command for Java installation:
apt-get update: Updates package repository listsapt-get install -y openjdk-17-jdk: Installs Java 17 JDK (LTS version)apt-get clean: Removes downloaded package filesrm -rf /var/lib/apt/lists/*: Deletes cached package lists to reduce image size
# Set JAVA_HOME
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64Lines 13-14:
- Comment explaining Java environment setup
- Sets JAVA_HOME environment variable for Java applications
# Create app directory
WORKDIR /appLines 16-17:
- Comment describing working directory creation
- Sets
/appas the working directory for subsequent commands
# Copy Java source file
COPY HelloWorldServer.java .Lines 19-20:
- Comment explaining file copy operation
- Copies Java source file from host to container's
/appdirectory
# Compile Java application
RUN javac HelloWorldServer.javaLines 22-23:
- Comment describing compilation step
- Compiles Java source code into bytecode (.class file)
# Expose port 8080
EXPOSE 8080Lines 25-26:
- Comment explaining port exposure
- Documents that container listens on port 8080 (metadata for Docker)
# Run the web server
CMD ["java", "HelloWorldServer"]Lines 28-29:
- Comment describing container startup command
- Executes Java application when container starts (exec form for better signal handling)
This project now uses an enterprise-grade CI/CD pipeline with multiple workflows for different purposes:
Purpose: Validates pull requests before merging Triggers: Pull requests to main/develop branches only
Purpose: Builds, tests, scans, and publishes images Triggers: Push to main, develop, feature/, hotfix/ branches (NOT PRs)
Purpose: Auto-deploy to development environment Triggers: Push to develop branch, manual dispatch
Purpose: Controlled production deployments with approval gates Triggers: Manual dispatch only
This file has been DEPRECATED and disabled to prevent accidental production deployments.
name: Build and Deploy to Docker Hub and KubernetesLine 1: Workflow name displayed in GitHub Actions interface
on:
push:
branches:
- main
pull_request:
branches:
- mainLines 2-8: Trigger configuration:
- Runs on pushes to main branch
- Runs on pull requests targeting main branch
- Ensures code is tested before merging
env:
DOCKER_IMAGE_NAME: java-hello-worldLines 10-11: Environment variables:
- Defines Docker image name used throughout the workflow
- Centralized configuration for easy maintenance
jobs:
build-and-deploy:
runs-on: ubuntu-latestLines 13-15: Job configuration:
- Single job named "build-and-deploy"
- Runs on latest Ubuntu runner (GitHub-hosted)
steps:
- name: Checkout code
uses: actions/checkout@v4Lines 17-19: First step - source code checkout:
- Uses official GitHub action to download repository code
- v4 is the latest stable version
- name: Set up Java JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'Lines 21-25: Java environment setup:
- Uses official Java setup action
- Installs Java 17 (LTS version matching Dockerfile)
- Uses Eclipse Temurin distribution (open-source OpenJDK)
- name: Compile Java application
run: javac HelloWorldServer.javaLines 27-28: Java compilation step:
- Compiles source code to verify it builds correctly
- Catches compilation errors before Docker build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3Lines 30-31: Docker Buildx setup:
- Enables advanced Docker build features
- Required for multi-platform builds
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}Lines 33-37: Docker Hub authentication:
- Uses official Docker login action
- Credentials stored as GitHub repository secrets
- Access token used instead of password for security
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=commit-
type=raw,value=latest,enable={{is_default_branch}}Lines 39-48: Docker image metadata generation:
- Creates tags and labels for Docker image
type=ref,event=branch: Tags with branch nametype=ref,event=pr: Tags with PR numbertype=sha,prefix=commit-: Tags with Git commit SHAtype=raw,value=latest: Tags as "latest" only on main branch
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64Lines 50-59: Docker build and push:
- Builds Docker image using Dockerfile
- Pushes to Docker Hub with generated tags
- Supports multiple architectures (AMD64 and ARM64)
- Uses build context from repository root
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'Lines 61-64: Kubernetes CLI setup:
- Installs kubectl for Kubernetes operations
- Uses specific version for consistency
- name: Configure kubectl
run: |
mkdir -p ~/.kube
printf '%s\n' "${{ secrets.KUBE_CONFIG }}" > ~/.kube/config
chmod 600 ~/.kube/configLines 66-70: Kubernetes configuration:
- Creates kubectl configuration directory
- Writes base64-encoded kubeconfig from secrets
- Sets secure file permissions (owner read/write only)
- name: Update Kubernetes deployment image
run: |
# Replace IMAGE_PLACEHOLDER with actual image
sed -i "s|IMAGE_PLACEHOLDER|${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:latest|g" k8s/deployment.yamlLines 73-76: Dynamic image reference update:
- Uses sed command to replace placeholder with actual image name
- Enables template-based deployment configuration
- Updates deployment.yaml with built image reference
- name: Deploy to Kubernetes
run: |
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yamlLines 78-83: Kubernetes deployment:
- Applies all Kubernetes manifests in order
- Creates namespace, deployment, service, and ingress
- Uses declarative configuration approach
- name: Wait for deployment
run: |
kubectl wait --for=condition=available --timeout=300s deployment/java-hello-world -n java-hello-worldLines 85-87: Deployment verification:
- Waits for deployment to become available
- 300-second timeout prevents infinite waiting
- Ensures pods are running before continuing
- name: Get deployment status
run: |
kubectl get pods -n java-hello-world
kubectl get services -n java-hello-world
kubectl get ingress -n java-hello-worldLines 89-93: Status reporting:
- Shows running pods in the namespace
- Displays service configuration and external IPs
- Shows ingress routing rules
- name: Deploy summary
run: |
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "Java web server application compiled successfully" >> $GITHUB_STEP_SUMMARY
echo "Docker image built and pushed to Docker Hub" >> $GITHUB_STEP_SUMMARY
echo "Application deployed to Kubernetes cluster" >> $GITHUB_STEP_SUMMARY
echo "**Image:** \`${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:latest\`" >> $GITHUB_STEP_SUMMARY
echo "**Docker Hub:** https://hub.docker.com/r/${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}" >> $GITHUB_STEP_SUMMARY
echo "**Kubernetes:** Deployed to namespace \`java-hello-world\`" >> $GITHUB_STEP_SUMMARYLines 95-103: Deployment summary creation:
- Creates formatted summary for GitHub Actions interface
- Uses GitHub-flavored Markdown for rich formatting
- Includes links and status indicators
- Provides deployment details for team visibility
- name: Notify Slack on successful build
if: success() && github.ref == 'refs/heads/main'
uses: 8398a7/action-slack@v3
with:
status: success
text: |
✅ Build and deployment completed successfully!
**Repository:** ${{ github.repository }}
**Branch:** ${{ github.ref_name }}
**Commit:** ${{ github.sha }}
**Author:** ${{ github.actor }}
**Image:** ${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:latest
**Kubernetes:** Deployed to namespace `java-hello-world`
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}Lines 105-120: Slack success notification:
- Triggers only on successful builds to main branch
- Uses community Slack action for webhook integration
- Includes comprehensive deployment information
- Formatted with emojis and markdown for readability
- name: Notify Slack on build failure
if: failure() && github.ref == 'refs/heads/main'
uses: 8398a7/action-slack@v3
with:
status: failure
text: |
❌ Build or deployment failed!
**Repository:** ${{ github.repository }}
**Branch:** ${{ github.ref_name }}
**Commit:** ${{ github.sha }}
**Author:** ${{ github.actor }}
**Workflow:** ${{ github.workflow }}
Please check the GitHub Actions logs for details.
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}Lines 122-137: Slack failure notification:
- Triggers only on failed builds to main branch
- Provides essential debugging information
- Directs users to GitHub Actions logs for troubleshooting
- Uses webhook URL stored in repository secrets
apiVersion: v1
kind: Namespace
metadata:
name: java-hello-world
labels:
name: java-hello-worldLine 1: Kubernetes API version for core resources Line 2: Resource type - Namespace for logical separation Line 4: Namespace name used across all resources Line 6: Label for resource identification and selection
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-hello-world
namespace: java-hello-world
labels:
app: java-hello-worldLines 1-7: Deployment metadata:
- Uses apps/v1 API for deployment resources
- Defines deployment name and target namespace
- Labels enable resource grouping and selection
spec:
replicas: 3Lines 8-9: Deployment specification:
- Creates 3 identical pod replicas for high availability
- Enables load distribution and fault tolerance
selector:
matchLabels:
app: java-hello-worldLines 10-12: Pod selector:
- Defines which pods this deployment manages
- Matches pods with "app: java-hello-world" label
template:
metadata:
labels:
app: java-hello-worldLines 13-16: Pod template metadata:
- Template for creating pods
- Labels must match selector for proper management
spec:
containers:
- name: java-hello-world
image: IMAGE_PLACEHOLDER
ports:
- containerPort: 8080Lines 17-22: Container specification:
- Single container per pod
- Image placeholder replaced during deployment
- Exposes port 8080 for HTTP traffic
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"Lines 23-29: Resource management:
requests: Minimum guaranteed resourceslimits: Maximum allowed resources- Prevents resource starvation and overconsumption
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 30
periodSeconds: 10Lines 30-35: Liveness probe configuration:
- Checks if container is alive by HTTP GET to "/"
- Waits 30 seconds before first check (startup time)
- Checks every 10 seconds thereafter
- Kubernetes restarts container if probe fails
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Lines 36-41: Readiness probe configuration:
- Checks if container is ready to receive traffic
- Shorter delays than liveness probe
- Kubernetes removes pod from service if probe fails
- Faster recovery for temporary issues
apiVersion: v1
kind: Service
metadata:
name: java-hello-world-service
namespace: java-hello-world
labels:
app: java-hello-worldLines 1-7: Service metadata:
- Core API version for service resources
- Service name used by other resources
- Same namespace and labels as deployment
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: httpLines 8-14: Service specification:
LoadBalancer: Provisions external load balancer (cloud provider)- Maps external port 80 to container port 8080
- TCP protocol for HTTP traffic
- Named port for reference in other resources
selector:
app: java-hello-worldLines 15-16: Pod selector:
- Routes traffic to pods with matching label
- Load balances across all healthy replicas
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: java-hello-world-ingress
namespace: java-hello-world
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /Lines 1-8: Ingress metadata and annotations:
- Networking API for ingress resources
- Nginx ingress controller configuration
- Rewrites incoming paths to root path "/"
spec:
rules:
- host: java-hello-world.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: java-hello-world-service
port:
number: 80Lines 9-20: Ingress routing rules:
- Routes requests for "java-hello-world.local" host
- Matches all paths starting with "/" (prefix match)
- Forwards traffic to the LoadBalancer service on port 80
- Enables hostname-based routing and SSL termination
- Java 17 or higher
- Docker (for containerization)
- kubectl (for Kubernetes deployment)
- Access to Kubernetes cluster
# Compile and run locally
javac HelloWorldServer.java
java HelloWorldServer
# Visit http://localhost:8080# Build Docker image
docker build -t java-hello-world .
# Run container
docker run -p 8080:8080 java-hello-world# Deploy all resources
kubectl apply -f k8s/
# Check deployment status
kubectl get all -n java-hello-world
# Get service external IP
kubectl get service java-hello-world-service -n java-hello-world- Development: Auto-deploy on
developbranch pushes - Production: Manual deployment with approval gates
- Manual Approval Gates: Production requires team approval
- Blue-Green Deployments: Zero-downtime production updates
- Automatic Rollbacks: Health check failures trigger rollbacks
| Action | Triggered Workflows |
|---|---|
Push to develop |
CI Build → Development Deployment |
Push to main |
CI Build only |
Push to feature/* |
CI Build only |
Push to hotfix/* |
CI Build only |
PR to main/develop |
PR Build Validation only |
| Manual production deploy | Production Deployment |
- GITHUB_TOKEN - Automatically provided for container registry
- KUBE_CONFIG_DEV - Development cluster kubeconfig (base64-encoded)
- KUBE_CONFIG_PROD - Production cluster kubeconfig (base64-encoded)
- SLACK_WEBHOOK_URL - Slack webhook URL for deployment notifications
Create these environments in your repository settings with appropriate protection rules:
development- No protection neededproduction-approval- Require approvers (team leads)production- Require approvers (senior engineers)
# Encode your kubeconfig
cat ~/.kube/config | base64 -w 0- Go to your Slack workspace
- Navigate to Apps → Incoming Webhooks
- Create a new webhook for your desired channel
- Copy the webhook URL (format:
https://hooks.slack.com/services/T.../B.../...) - Add it as
SLACK_WEBHOOK_URLsecret in your GitHub repository
The enterprise pipeline sends comprehensive Slack notifications for:
- ✅ Successful deployments to all environments
- ❌ Failed deployments with debugging information
- 🚀 Production deployments with special formatting
- 📋 PR validation results for team visibility
Notifications include:
- Environment and deployment details
- Image tags and commit information
- Health check results and deployment status
- Direct links to GitHub Actions logs
- Feature Development: Push to feature branches triggers CI builds only
- Pull Request Validation: PRs trigger validation workflow with comprehensive testing
- Development Deployment: Merge to
develop→ Auto-deploy to dev environment - Production Deployment: Manual trigger with approval gates and blue-green strategy
- Monitoring & Notifications: Health checks and Slack notifications
- Environment Separation: Isolated development and production environments
- Approval Gates: Manual approvals for production changes
- Zero Downtime: Blue-green deployments with automatic rollback
- Comprehensive Testing: Unit tests, integration tests, load tests
- Observability: Detailed logging, metrics, and notifications
- Multi-platform Support: AMD64 and ARM64 architectures
- Container Registry: GitHub Container Registry with proper authentication
- Port conflicts: Change port in Java code and Dockerfile
- Resource limits: Adjust requests/limits in deployment.yaml
- Image pull errors: Verify Docker Hub credentials and image name
- Service unavailable: Check pod status and health probes
- Ingress not working: Verify ingress controller and DNS configuration
# Check pod logs
kubectl logs -f deployment/java-hello-world -n java-hello-world
# Describe deployment for events
kubectl describe deployment java-hello-world -n java-hello-world
# Check service endpoints
kubectl get endpoints java-hello-world-service -n java-hello-world
# Test connectivity
kubectl port-forward service/java-hello-world-service 8080:80 -n java-hello-world# Clone repository
git clone <repository-url>
cd Github-Action-POC
# Create develop branch
git checkout -b develop
git push -u origin develop- Set up required secrets and environments (see GitHub Actions Setup section)
- Configure branch protection rules for
mainanddevelop - Set up required approvers for production environment
# Create feature branch
git checkout -b feature/my-new-feature
# Make changes and push (triggers CI build only)
git add .
git commit -m "Add new feature"
git push -u origin feature/my-new-feature
# Create PR to develop (triggers PR validation)
# After review, merge to develop (triggers dev deployment)
# Create PR from develop to main (triggers PR validation)
# After review, merge to main (triggers CI build only)
# For production: Use GitHub Actions UI to manually deploy- Go to Actions → Deploy to Production
- Click Run workflow
- Enter image tag (e.g., latest commit SHA from main branch)
- Wait for approvals and monitor deployment
If migrating from the old single workflow:
- Review the DEPLOYMENT_GUIDE.md for complete setup instructions
- The old workflow has been disabled - only runs on manual emergency trigger
- Test the new pipeline in development first
- Gradually migrate to the new branching strategy
This documentation provides a complete understanding of the enterprise-grade CI/CD pipeline, enabling teams to develop, test, and deploy applications safely and efficiently across multiple environments.