Hackathons are fun, but stressful β especially when your site must stay live until the judging ends.
Hereβs a complete copy-resistant, step-by-step deployment guide you can drop into your repo and follow.
Itβs designed around:
- Gradle backend (Spring Boot)
- React frontend (Nginx)
- MySQL DB
- Multi-stage Docker builds
- Jenkins CI/CD
- Hostinger VPS (Ubuntu)
- Blue/Green-style release strategy (atomic swap + health-gated rollback)
By the end, youβll have a live, SSL-enabled site with short downtime, safe rollback, and reproducible builds.
π Whatβs Unique in This Approach
-
Deterministic image tags β
YYYYmmddHHMM-<gitshort>
(e.g.,202509041530-1a2b3c4
). -
Atomic release swap β Jenkins deploys to
/opt/chattingo/releases/<TAG>
, then atomically swaps to/opt/chattingo/current
. -
Health-gated rollback β If the new release fails
/actuator/health
within 60s, rollback triggers automatically. -
Cache-busting frontend β Docker build injects a unique
BUILD_ID
intoindex.html
. - Minimal downtime β Users never see a broken app.
- Reproducible & transparent β All scripts, configs, and Jenkinsfile are in the repo.
π οΈ Prerequisites
Before starting, fill in these placeholders with your values:
-
YOUR_DOCKERHUB_USER
β DockerHub username -
YOUR_DOMAIN
β domain you own (for SSL) -
YOUR_VPS_IP
β Hostinger VPS IP -
SSH_USER
β VPS user (e.g.,ubuntu
) -
GIT_BRANCH
β branch used for deployment (e.g.,devops-implementation
) -
JENKINS_CRED_IDS
β Jenkins credentials (dockerhub-user/pass
,vps-ssh
)
ποΈ Docker & Compose Setup
1. Frontend Dockerfile
Injects build ID for cache busting:
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG BUILD_ID
RUN if [ -f public/index.html ]; then sed -i "s/__BUILD_ID__/${BUILD_ID}/g" public/index.html || true; fi
RUN npm run build
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY nginx.frontend.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
β‘οΈ Add __BUILD_ID__
in your public/index.html
to avoid stale caches.
2. Backend Dockerfile (Gradle multi-stage)
FROM gradle:8.3-jdk17 AS builder
WORKDIR /app
COPY build.gradle settings.gradle ./
COPY gradle ./gradle
COPY src ./src
RUN gradle clean build -x test --no-daemon
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8080
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=5 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java","-jar","/app/app.jar"]
β‘οΈ No need to install Gradle on Jenkins β build happens inside the container.
3. docker-compose.yml (release template)
version: "3.8"
services:
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpass}
MYSQL_DATABASE: ${MYSQL_DATABASE:-chattingo}
volumes:
- db_data:/var/lib/mysql
restart: unless-stopped
backend:
image: YOUR_DOCKERHUB_USER/chattingo-backend:PLACEHOLDER_TAG
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/${MYSQL_DATABASE}
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD}
depends_on:
- db
ports:
- "8080:8080"
restart: unless-stopped
frontend:
image: YOUR_DOCKERHUB_USER/chattingo-frontend:PLACEHOLDER_TAG
depends_on:
- backend
ports:
- "3001:80"
restart: unless-stopped
volumes:
db_data:
β‘οΈ Jenkins replaces PLACEHOLDER_TAG
with your unique $TAG
.
π Jenkins Pipeline (CI/CD)
Your Jenkinsfile
:
pipeline {
agent any
environment {
IMAGE_PREFIX = "YOUR_DOCKERHUB_USER"
VPS_SSH = "vps-ssh"
BRANCH = "GIT_BRANCH"
}
stages {
stage('Checkout') { steps { checkout scm } }
stage('Set TAG') {
steps {
script {
env.TAG = sh(
script: "echo $(date +%Y%m%d%H%M)-$(git rev-parse --short HEAD)",
returnStdout: true
).trim()
}
}
}
stage('Build Frontend Image') {
steps {
sh "docker build --build-arg BUILD_ID=${TAG} -t ${IMAGE_PREFIX}/chattingo-frontend:${TAG} ./frontend"
}
}
stage('Build Backend Image') {
steps {
sh "docker build -t ${IMAGE_PREFIX}/chattingo-backend:${TAG} ./backend"
}
}
stage('Push Images') {
steps {
withCredentials([usernamePassword(credentialsId: 'dockerhub-creds', usernameVariable: 'USER', passwordVariable: 'PASS')]) {
sh '''
echo $PASS | docker login -u $USER --password-stdin
docker push ${IMAGE_PREFIX}/chattingo-frontend:${TAG}
docker push ${IMAGE_PREFIX}/chattingo-backend:${TAG}
'''
}
}
}
stage('Deploy to VPS') {
steps {
sshagent (credentials: [env.VPS_SSH]) {
sh '''
scp docker-compose.yml ${SSH_USER}@${YOUR_VPS_IP}:/opt/chattingo/releases/${TAG}/docker-compose.yml
ssh ${SSH_USER}@${YOUR_VPS_IP} "sudo /usr/local/bin/deploy_release.sh ${TAG}"
'''
}
}
}
}
}
π’ Atomic Deployment Script (on VPS)
Save as /usr/local/bin/deploy_release.sh
:
#!/usr/bin/env bash
set -euo pipefail
TAG="$1"
RELEASE_DIR="/opt/chattingo/releases/${TAG}"
CURRENT_DIR="/opt/chattingo/current"
PREV_DIR="/opt/chattingo/previous"
TIMEOUT=60
HEALTH_URL="https://YOUR_DOMAIN/actuator/health"
if [ ! -d "${RELEASE_DIR}" ]; then exit 2; fi
# Swap logic
[ -d "${CURRENT_DIR}" ] && mv "${CURRENT_DIR}" "${PREV_DIR}"
cp -r "${RELEASE_DIR}" "${CURRENT_DIR}"
cd "${CURRENT_DIR}"
docker compose up -d --remove-orphans
# Health gating
SECONDS=0
until curl -fsS "${HEALTH_URL}" >/dev/null; do
sleep 5
[ $SECONDS -ge $TIMEOUT ] && {
docker compose down || true
[ -d "${PREV_DIR}" ] && mv "${PREV_DIR}" "${CURRENT_DIR}" && cd "${CURRENT_DIR}" && docker compose up -d
exit 3
}
done
rm -rf "${PREV_DIR}"
β‘οΈ This ensures instant rollback if your site fails health checks.
π Nginx Reverse Proxy
Place at /etc/nginx/sites-available/chattingo
:
server {
listen 80;
server_name YOUR_DOMAIN;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name YOUR_DOMAIN;
ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3001/;
}
location /api/ {
proxy_pass http://127.0.0.1:8080/api/;
}
}
Then:
sudo ln -s /etc/nginx/sites-available/chattingo /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
β images:
β¨ Thanks for reading! If this guide helped you,
π Like β€οΈ the post to support my work
π Comment π¬ your thoughts, questions, or improvements
π Share π with friends or teammates working on CI/CD or hackathon projects
Letβs keep building and learning together π
Top comments (0)