🚀
DevOps Journey Zero to Production Hostinger KVM 2 • Ubuntu 26.04 • Spring Boot • Nginx
📖 Chapters ⚡ Labs 💻 Commands ⚖️ Comparisons 🧠 Quiz
Phase 1: Linux & SSH Phase 2: Networking & TLS Phase 3: Systemd & Proxy Phase 4: Blue-Green & Ops

DevOps Mastery: Zero to Production

A comprehensive, interactive, visual guide documenting the complete real-world journey of transforming a bare Hostinger VPS into a hardened, high-availability, zero-downtime Blue-Green production deployment.

Server Environment
Provider: Hostinger KVM 2
OS: Ubuntu 26.04.1 LTS
CPU / RAM: 2 vCPU / ~7.7 GiB
Disk: 96 GiB NVMe
Public IP: 200.234.43.151
Domain: firozjaroli.tech
1

Phase 1: Bare Linux VPS Setup, Sudo & SSH Hardening

Security & System Init

When provisioning a fresh virtual server, the first priority is establishing system baseline metrics, creating a dedicated non-root administrative account, and hardening SSH to eliminate password brute-force and root-login attack vectors.

🧑‍💻
Curious Student: "Why shouldn't I just keep logging in directly as root? It saves me typing sudo before every single command!"
🧑‍🏫
DevOps Mentor: "The root user has UID 0 and bypasses all filesystem permissions without warnings. A simple typo like rm -rf / tmp could destroy your entire OS in a fraction of a second. By creating a dedicated user (firoz) and granting sudo rights, every privileged execution is conscious, logged in /var/log/auth.log, and separated by privilege boundaries."

1.1 System Inspection & User Creation

We inspect available memory with free -h (~7.7 GiB RAM), disk space with df -h / (96 GiB), and CPU cores with nproc (2 vCPUs). We then create a standard admin user and add them to the sudoers group.

$ sudo adduser firoz
$ sudo usermod -aG sudo firoz
$ id firoz
# Output: uid=1000(firoz) gid=1000(firoz) groups=1000(firoz),27(sudo)

1.2 SSH Hardening & Strict Permissions

We copy the public SSH key to /home/firoz/.ssh/authorized_keys with strict permissions (700 dir, 600 file), and create a modular hardening config in /etc/ssh/sshd_config.d/00-hardening.conf.

$ sudo chmod 700 ~/.ssh && sudo chmod 600 ~/.ssh/authorized_keys
$ sudo nano /etc/ssh/sshd_config.d/00-hardening.conf
# PermitRootLogin no | PasswordAuthentication no | PubkeyAuthentication yes
$ sudo sshd -t && sudo systemctl reload ssh
⚠️
The SSH Hardening Golden Rule: Never close your existing active SSH terminal session immediately after reconfiguring SSH! Always validate syntax with sudo sshd -t, verify effective rules with sudo sshd -T, and open a NEW, separate terminal window to test ssh firoz@200.234.43.151 before terminating your session.

1.3 UFW Firewall Activation

Linux iptables/netfilter is managed cleanly via UFW (Uncomplicated Firewall). We enforce a strict default deny incoming policy, allow outbound connections, and explicitly whitelist TCP port 22 for SSH.

$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw allow 22/tcp
$ sudo ufw enable
$ sudo ufw status verbose
Status: active
Default: deny (incoming), allow (outgoing)
22/tcp ALLOW IN Anywhere
2

Phase 2: Networking, Nginx Web Server, DNS & HTTPS

Web Gateway & TLS

In Phase 2, we turn the VPS into an internet-facing web server. We explore Linux networking, understand socket binding (localhost vs 0.0.0.0), configure Nginx Virtual Hosts, point real DNS records, and secure traffic with automated Let's Encrypt TLS certificates.

🧑‍💻
Curious Student: "Why does Nginx have both sites-available and sites-enabled directories? Why not just put all configuration files in one single folder?"
🧑‍🏫
DevOps Mentor: "Think of sites-available as your configuration vault containing all website blueprints. sites-enabled contains only the active websites. By creating a symbolic link (symlink) from sites-available/mysite into sites-enabled/, you can enable or disable a site in 1 second with rm without deleting or modifying your original configuration!"

2.1 Deep Dive: How Symbolic Links (Symlinks) Work in Linux

A symlink (soft link) is a special file whose content is a textual path pointing to another file's inode on the filesystem. When Nginx reads /etc/nginx/sites-enabled/learning-site, the Linux kernel automatically resolves the pointer to /etc/nginx/sites-available/learning-site.

$ sudo ln -s /etc/nginx/sites-available/learning-site /etc/nginx/sites-enabled/learning-site
$ ls -l /etc/nginx/sites-enabled/learning-site
lrwxrwxrwx 1 root root 46 Sep 1 10:14 learning-site -> /etc/nginx/sites-available/learning-site
# Notice the 'l' in lrwxrwxrwx denoting a symbolic link!

2.2 DNS Configuration & Name Resolution

DNS maps human-readable domain names to numerical IP addresses. We configured:

  • A Record: @ (firozjaroli.tech) -> 200.234.43.151
  • CNAME: www -> firozjaroli.tech
  • A Record: api -> 200.234.43.151 (for Spring Boot API)
$ getent ahostsv4 firozjaroli.tech

2.3 HTTPS & Let's Encrypt TLS Automation

Certbot interacts with Let's Encrypt via the ACME HTTP-01 challenge, automatically installs SSL certificates, updates Nginx server blocks, and sets up 301 HTTP-to-HTTPS redirects.

$ sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
$ sudo certbot --nginx -d firozjaroli.tech -d www.firozjaroli.tech
# Certificate issued & auto-renewal timer enabled
3

Phase 3: Spring Boot Deployment, systemd & Reverse Proxying

App Supervision & Isolation

In Phase 3, we build and package a real Java 21 Spring Boot executable JAR, deploy it to /opt/devops-learning/, manage its lifecycle as a resilient background service using systemd, bind it strictly to 127.0.0.1 for security isolation, and expose it through Nginx as a reverse proxy under api.firozjaroli.tech.

🧑‍💻
Curious Student: "Why did we configure Spring Boot to listen on 127.0.0.1 instead of letting it listen on 0.0.0.0? And why do we need Nginx in front of it?"
🧑‍🏫
DevOps Mentor: "Binding to 127.0.0.1 guarantees that Spring Boot is completely invisible to the external internet. Only processes inside the VPS can connect to it. Nginx acts as our secure front door: it terminates TLS encryption, handles rate limiting, caches static assets, and proxies sanitized HTTP requests internally to Spring Boot. If an attacker scans your server, they cannot attack Tomcat directly!"

3.1 Anatomy of a Production systemd Unit File

Running java -jar in a terminal is fragile because closing the SSH session kills the process. We create /etc/systemd/system/devops-learning.service to give the JVM supervision, automatic crash restart, and boot persistence.

/etc/systemd/system/devops-learning.service
[Unit]
Description=DevOps Learning Application
After=network.target # Wait until Linux network stack is up

[Service]
User=firoz # Run as non-root user for security isolation
WorkingDirectory=/opt/devops-learning
ExecStart=/usr/bin/java -jar /opt/devops-learning/current.jar --server.port=8080
Restart=on-failure # Automatically resurrect app if it crashes!
RestartSec=5s

[Install]
WantedBy=multi-user.target # Start during standard multi-user system boot

3.2 Nginx Reverse Proxy Configuration (devops-api)

We configure /etc/nginx/sites-available/devops-api to intercept requests for api.firozjaroli.tech and proxy them over localhost with forwarded client headers.

server_name api.firozjaroli.tech;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
4

Phase 4: Production Ops, Blue-Green Deployments, Graceful Shutdown & Backups

Zero-Downtime Architecture

Phase 4 elevates our server to true enterprise operational standards: implementing health check telemetry, versioned artifact storage, zero-downtime atomic symlink switching, SIGTERM graceful shutdown handling, fixed-environment Blue-Green deployments, and automated compressed disaster recovery backups.

🧑‍💻
Curious Student: "Why did we switch from dynamic port naming (Blue :8080 / Green :8081 / v3 :8082) to fixed environments (Blue :8081 and Green :8082)?"
🧑‍🏫
DevOps Mentor: "Dynamic naming is an operational nightmare! You would have to constantly create new systemd service files, open new firewall ports, edit monitoring alarms, and clean up orphaned services. With fixed environments (Blue :8081 / Green :8082), your infrastructure is immutable and rock-solid. You deploy the new JAR to the idle slot, health-check it, switch Nginx, and keep the old slot ready for instant rollback!"

4.1 Versioned Releases & Atomic Switching

Never overwrite devops-learning.jar directly in production! Instead, store immutable artifacts in releases/ and atomically update symlinks with ln -sfn.

$ sudo mkdir -p /opt/devops-learning/releases
$ sudo mv app.jar releases/devops-learning-v4.jar
$ sudo ln -sfn releases/devops-learning-v4.jar current.jar
# Instant kernel-level atomic pointer update (Zero flicker!)

4.2 Graceful Shutdown & Exit Status 143

Spring Boot configured with server.shutdown=graceful catches SIGTERM (Signal 15) from systemd, stops accepting new connections, drains active in-flight HTTP requests, and exits cleanly with code 143 ($128 + 15 = 143$).

$ sudo systemctl show devops-learning -p TimeoutStopUSec -p KillSignal
TimeoutStopUSec=1min 30s | KillSignal=15

4.3 Fixed-Environment Blue-Green Deployment Pattern

🔵 Blue Environment
Port: 127.0.0.1:8081
Service: devops-blue.service
Symlink: /opt/devops-learning/blue/current.jar
🟢 Green Environment
Port: 127.0.0.1:8082
Service: devops-green.service
Symlink: /opt/devops-learning/green/current.jar

4.4 Backups & Disaster Recovery (The 3-2-1 Rule)

We measured our total deployment footprint (/opt ~57M, /etc/nginx ~96K, systemd ~12K) and created compressed tar archives.

$ sudo tar -czf ~/backups/devops-learning-$(date +%F).tar.gz /opt/devops-learning /etc/nginx /etc/systemd/system/devops-*.service
$ tar -tzf ~/backups/devops-learning-$(date +%F).tar.gz | head -n 10
DevOps Backup Axiom: A backup stored on the exact same VPS is NOT disaster recovery! Follow the 3-2-1 Rule: 3 copies of data, on 2 different storage media types, with at least 1 copy stored OFF-SITE (AWS S3, Google Cloud Storage, or offsite NAS).