I architect reliable, high-availability systems with a quality-first mindset. Currently, I'm building a series of DevOps projects designed to solve real-world infrastructure challenges. I'm passionate about creating the kind of automated, scalable systems that empower teams to ship better software at high speed.
I've been a QA Engineer at Rossmann since 2023, on a cross-functional team that plans work in Kanban and Jira. Day to day, I test the POS Eco-System, backend APIs, and integration services running on GKE β a mix of manual testing and automation testing with Eggplant (Keysight). Alongside that, I handle ad-hoc Linux server tasks, help coordinate release and update planning, and regularly weigh in on infrastructure and tooling decisions for the team.
Being this close to the infrastructure β not just the software running on top of it β is what pulled me toward DevOps: I want to move from verifying systems to building and operating them myself.
B.Sc. in Data Science completed. That same hands-on exposure to Linux and GKE infrastructure in my QA role is what pushed me to pursue an M.Sc. in DevOps & Cloud Computing at IU International University, graduating May 2027 β a deliberate, structured path to turn infrastructure curiosity into formal skills in CI/CD, IaC, and container orchestration.
A mix of shipped work and an active 12-month roadmap. Each project links to its GitHub repo where the code and setup are documented in full.
Hardened a Hetzner VPS from scratch: SSH key auth, UFW firewall, and Docker running Nginx as a reverse proxy in front of the app, fronted by Cloudflare CDN with SSL via Let's Encrypt. This same stack now hosts this portfolio site.
This website itself: a GitHub Actions workflow that SCPs index.html straight to the VPS on every push to main, using repo secrets for the SSH key β no manual deploys.
NLP pipeline analysing 4.1M tweets across 5 stocks (TSLA, NVDA, PLTR, PG, NEE) using FinBERT. Correlated sentiment scores against price movement with Spearman correlation. From my B.Sc. Data Science work.
Containerising a B.Sc. Python project from scratch β writing the Dockerfile, not just pulling images. First real step past tutorial-level Docker usage.
End-to-end automated pipeline with integrated security scanning and artifact management, extending the existing deploy workflow with real build/test stages.
Provisioning a real GCP resource with Terraform β modular, reproducible infrastructure managed entirely through code instead of the console.
Fully automated GitOps deployments with service mesh and Prometheus monitoring on GCP GKE. Infrastructure and application state managed via Git.
30 hand-picked commands, each tied to a real scenario β not just what the flag does, but why you'd reach for it and what result to expect on screen.
Scenario: A server feels sluggish and you need to know why right now.
Result: A live, refreshing table of every process ranked by CPU/memory β the runaway process is usually sitting at the top.
Scenario: You want a one-shot snapshot of memory hogs to paste into a ticket, not a live dashboard.
Result: Every process on the system, sorted highest memory first β the --sort flag is the useful part most people miss.
Scenario: A process is hung and ignoring a normal kill/Ctrl+C.
Result: SIGKILL terminates it immediately, no cleanup β last resort, since the app can't close files or flush data first.
Scenario: You need to kill a process by name because you don't know (or don't want to look up) its PID.
Result: Matches the full command line via -f and kills every matching process β handy after a bad deploy leaves zombie workers.
Scenario: A site is down and you need to know if the service is even running before debugging further.
Result: Shows active/failed state, the PID, recent log lines, and enabled-on-boot status in one screen.
Scenario: Deploys are failing with cryptic "no space left on device" errors.
Result: Disk usage per mounted filesystem in human-readable GB/MB (not raw block counts) β instantly shows which partition is full.
Scenario: df says the disk is full β now you need to find which directory is actually eating the space.
Result: Total size per item, human-readable; piped through sort -rh the biggest offender lands right at the top.
Scenario: You need to clean up log files older than a month without deleting anything recent by accident.
Result: Lists every matching file untouched in 30+ days β verify the list, then re-run with -delete to actually remove them.
Scenario: You need one portable file to move or archive an entire app directory before a risky change.
Result: A single gzip-compressed archive; -v prints each file as it's added so you can confirm nothing was skipped.
Scenario: Deploying an updated app directory to a remote server without re-uploading every file each time.
Result: Only changed files transfer (fast re-runs); --delete also removes files on the remote that no longer exist locally, keeping both sides in sync.
Scenario: "Address already in use" when starting a service β something else already has the port.
Result: Prints the exact process name and PID bound to port 8080, so you can decide to kill it or pick another port.
Scenario: You just deployed a service and need to confirm it's actually listening before testing externally.
Result: Every TCP/UDP port currently listening, with the owning process name and PID β the modern, faster replacement for netstat.
Scenario: A service should be up β you want to check the HTTP status without downloading the whole page.
Result: Just the response headers (status code, server, content-type) β a 200 confirms it's alive, a 502/timeout tells you where to dig next.
Scenario: A server can't reach the internet or another host β is it DNS, routing, or the network entirely?
Result: 4 ICMP replies with latency; -c stops it automatically instead of pinging forever. No reply at all usually means a firewall or routing issue.
Scenario: Connecting to a fresh VPS that only accepts key-based auth, not passwords.
Result: Opens an authenticated remote shell using the specified private key β the flag to know when the server has multiple keys configured.
Scenario: You need to copy a whole build folder to a server without setting up rsync.
Result: Recursively copies the directory and its contents over SSH, encrypted in transit, landing at the given remote path.
Scenario: DNS was just changed (new A record, Cloudflare proxy) and you need to verify it propagated.
Result: Just the resolved IP address(es), no verbose DNS trace noise β quick to eyeball or script against.
Scenario: Nginx won't start and you suspect a permissions or ownership problem in its config directory.
Result: Every entry including hidden dotfiles (-a), in long format showing owner/group/permissions (-l), with sizes in human-readable K/M/G (-h) instead of raw bytes.
Scenario: A deploy script fails with "permission denied" when you try to run it.
Result: Owner gets read/write/execute, group and others get read/execute β the standard mode for a script everyone should be able to run but only you edit.
Scenario: Nginx (running as www-data) can't read files you just uploaded as root.
Result: Recursively hands ownership of the whole directory tree to the www-data user and group so the web server can serve it.
Scenario: You need to run a command as a specific service account (e.g. the DB user), not as root.
Result: Runs the command with that user's identity and environment β safer than switching to root for a task that only needs one user's privileges.
Scenario: Setting up a new VPS and you don't want to keep working as root.
Result: Creates a user with a home directory (-m) and bash as the login shell (-s) β ready for SSH key setup and sudo access.
Scenario: Something failed overnight and you need every occurrence of an error across a whole log directory.
Result: Every matching line recursively (-r) across all files, prefixed with the line number (-n) so you can jump straight to it.
Scenario: An access log has 12 columns and you only care about the IP and status code.
Result: Prints just the chosen whitespace-delimited columns per line β far faster than eyeballing full log lines.
Scenario: A config file was accidentally deployed with debug logging left on.
Result: Replaces every occurrence in place (-i writes the file directly) β no need to open an editor for a one-line fix.
Scenario: You just restarted a service and want to watch what happens as it starts.
Result: Prints the last 100 lines then keeps streaming new ones live β Ctrl+C to stop following.
Scenario: You want to know how many errors happened today, not read them all.
Result: A single number β the line count from whatever was piped in β great for quick sanity checks or scripting alerts.
Scenario: A backup or cleanup script needs to run automatically every night without you SSHing in manually.
Result: Opens the current user's cron table in an editor β add a line like 0 2 * * * /opt/backup.sh and it runs at 2am daily.
Scenario: You start a long-running script over SSH but need to log off before it finishes.
Result: The process keeps running after the SSH session ends (immune to hangup signals); output is redirected to a file instead of vanishing with the terminal.
Scenario: You found a list of files to delete with find, but find ... -delete isn't flexible enough for the cleanup command you need.
Result: Takes each line from stdin and appends it as an argument to the given command β turns a list of paths into one batch rm call.
Scenario: You ran a complex command last week and can't remember the exact flags.
Result: Every past shell command containing "docker", with its history number β rerun one instantly with !<number>.
Scenario: A script works locally but fails on the server with "command not found" β likely a PATH or missing env var issue.
Result: Lists every environment variable currently set in the shell, letting you confirm PATH, NODE_ENV, or similar are what the app expects.
25 commands covering the day-to-day Docker workflow β building and managing images, running and debugging containers, volumes and networks, and Compose.
Build an image from a Dockerfile in the current directory.
List all locally stored images.
Download an image from a registry.
Upload an image to a registry.
Tag an image with a new name or version.
Remove one or more local images.
Create and start a new container from an image.
List containers β add -a to include stopped ones.
Gracefully stop a running container.
Start a previously stopped container.
Restart a container.
Remove a stopped container.
Run a command inside a running container, e.g. an interactive shell.
Follow a container's log output in real time.
Show detailed low-level info about a container or image.
Show the running processes inside a container.
Live stream of CPU/memory/network usage per container.
Copy files between a container and the host.
List Docker-managed volumes.
List Docker networks.
Show a network's config and connected containers.
Start all services defined in docker-compose.yml, detached.
Stop and remove containers/networks created by Compose.
Follow log output from all Compose services.
Remove unused containers, images, and networks to free space.
24 commands covering the daily Git workflow β setup, the local commit cycle, branching and merging, working with remotes, inspecting history, and undoing mistakes safely.
Turn the current directory into a new Git repository.
Download a full copy of a remote repository, including its history.
Set identity/preferences that apply across all repos for this user.
Show all effective Git configuration values currently in use.
Show staged, unstaged, and untracked changes in the working directory.
Stage file changes to be included in the next commit.
Save staged changes to history with a descriptive message.
Show line-by-line changes not yet committed (add --staged for staged ones).
List local (and with -a, remote-tracking) branches.
Create and switch to a new branch in one step.
Integrate another branch's commits into the current branch.
Replay current branch commits on top of another branch for a linear history.
Delete a local branch once it's merged.
Show the remote repositories this repo is linked to.
Upload local commits to a remote branch (-u sets tracking for future pushes).
Fetch and integrate remote changes into the current branch.
Download remote history without merging it into your working branch.
Compact, visual view of commit history across all branches.
Show the full diff and metadata for a single commit.
Show which commit last changed each line of a file.
Unstage a file, or discard unstaged local edits back to the last commit.
Move the branch pointer back a commit, discarding changes since then.
Create a new commit that undoes a previous one β safe on shared history.
Temporarily shelve uncommitted changes, then reapply them later.
25 kubectl commands for day-to-day cluster work β context switching, workloads, deployments and scaling, services and networking, debugging, and namespaces/secrets.
Show the cluster's control-plane and service endpoints.
List available kubeconfig contexts.
Switch the active cluster context.
Show client and server Kubernetes versions.
List pods in the current namespace.
Show detailed info and recent events for a pod.
Stream logs from a pod or container.
Run a command inside a running pod.
Create or update resources from a manifest file.
Delete resources defined in a manifest file.
Watch the progress of a deployment rollout.
List deployments and their replica status.
Change the replica count of a deployment.
Roll a deployment back to its previous revision.
Update a deployment's container image and trigger a rollout.
List services and their cluster/external IPs.
Forward a local port to a pod or service.
List ingress rules and the hosts they route.
List cluster nodes and their status.
Show live CPU/memory usage per pod (needs metrics-server).
Show recent cluster events, most recent last.
Open a live resource in your editor to modify it directly.
List all namespaces in the cluster.
Create a Secret from literal values or files.
List ConfigMaps in the current namespace.