home.sh
Suleman Butt
www.sulemanb.com
$ whoami

QA Engineer β†’ DevOps Engineer_

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.

πŸ’Ό Professional Experience

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.

  • QA Engineering (POS / Backend APIs / GKE) Active
  • Automation Testing (Eggplant / Keysight) Active
  • Release & Update Planning Active
  • Ad-hoc Linux Server Tasks Active
  • Infrastructure & Tooling Input Active
  • Google Cloud Platform (consumer) Active
  • Datadog Observability (dashboards) Active

πŸŽ“ Academic Journey

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.

  • B.Sc. Data Science βœ“ Completed
  • M.Sc. DevOps & Cloud Computing In Progress
  • ISTQB Foundation βœ“ Achieved
  • ISAQB Foundation Sep 2026
  • GCP Associate Cloud Engineer Planned

Global Skill Stack

hands-on
Linux
Docker
Git
Python
SQL
GCP (consumer)
Datadog
Jira
Eggplant / Keysight
currently learning β€” M.Sc. DevOps & Cloud Computing
Kubernetes
Terraform
GitLab CI
CI/CD Pipelines
$ contact --show

Get In Touch_

πŸ’Ό

LinkedIn

Visit Profile β†’
πŸ“§

Email

Send Email β†’
projects.sh
$ ls -la ~/projects

πŸ“¦ Projects_

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.

# shipped
Completed

Secure Edge-to-Container Stack

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.

VPSUbuntuDockerNginxCloudflare
View Project GitHub β†’
Completed

Portfolio CI/CD to VPS

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.

GitHub ActionsSCPVPS
View Project GitHub β†’
Completed

Stock Sentiment Analysis

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.

PythonFinBERTNLPPandasData Science
View Project GitHub β†’
# in progress / next up
Coming Soon

First Hand-Written Dockerfile

Containerising a B.Sc. Python project from scratch β€” writing the Dockerfile, not just pulling images. First real step past tutorial-level Docker usage.

DockerPython
Coming Soon

CI/CD Pipeline Automation

End-to-end automated pipeline with integrated security scanning and artifact management, extending the existing deploy workflow with real build/test stages.

GitHub ActionsDockerSonarQube
Coming Soon

Cloud Infrastructure as Code

Provisioning a real GCP resource with Terraform β€” modular, reproducible infrastructure managed entirely through code instead of the console.

TerraformGCP
# advanced / long-term
Coming Soon

Kubernetes GitOps Platform

Fully automated GitOps deployments with service mesh and Prometheus monitoring on GCP GKE. Infrastructure and application state managed via Git.

KubernetesArgoCDHelmPrometheusGCP GKE
linux.sh
$ man linux-essentials

🐧 Linux Commands for DevOps_

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.

# system & process
$ top
top

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.

$ ps aux --sort=-%mem
ps aux --sort=-%mem | head

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.

$ kill -9
kill -9 1234

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.

$ pkill -f
pkill -f "node server.js"

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.

$ systemctl status
systemctl status nginx

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.

# disk & files
$ df -h
df -h

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.

$ du -sh
du -sh /var/log/* | sort -rh | head

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.

$ find -mtime
find /var/log -name "*.log" -mtime +30

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.

$ tar -czvf
tar -czvf backup.tar.gz ./app

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.

$ rsync -avz
rsync -avz --delete ./app/ user@host:/srv/app/

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.

$ lsof -i
lsof -i :8080

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.

# networking
$ ss -tulpn
ss -tulpn

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.

$ curl -I
curl -I https://example.com

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.

$ ping -c
ping -c 4 8.8.8.8

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.

$ ssh -i
ssh -i ~/.ssh/id_ed25519 [email protected]

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.

$ scp -r
scp -r ./dist user@server:/var/www/app

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.

$ dig +short
dig example.com +short

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.

# permissions & users
$ ls -alh
ls -alh /etc/nginx

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.

$ chmod 755
chmod 755 deploy.sh

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.

$ chown -R
chown -R www-data:www-data /var/www/app

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.

$ sudo -u
sudo -u postgres psql

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.

$ useradd -m -s
useradd -m -s /bin/bash deploy

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.

# text processing & search
$ grep -rn
grep -rn "ERROR" /var/log/app

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.

$ awk '{print}'
awk '{print $1, $9}' access.log

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.

$ sed -i
sed -i 's/DEBUG/INFO/g' config.yml

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.

$ tail -f
tail -f -n 100 /var/log/syslog

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.

$ wc -l
grep "ERROR" app.log | wc -l

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.

# automation & environment
$ crontab -e
crontab -e

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.

$ nohup ... &
nohup ./long-job.sh > out.log 2>&1 &

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.

$ xargs
find . -name "*.tmp" | xargs rm -f

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.

$ history | grep
history | grep docker

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>.

$ env
env | grep -i path

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.

docker.sh
$ docker --help

🐳 Docker Essentials_

25 commands covering the day-to-day Docker workflow β€” building and managing images, running and debugging containers, volumes and networks, and Compose.

# images
$ docker build
docker build -t myapp:latest .

Build an image from a Dockerfile in the current directory.

$ docker images
docker images

List all locally stored images.

$ docker pull
docker pull nginx:alpine

Download an image from a registry.

$ docker push
docker push myrepo/myapp:latest

Upload an image to a registry.

$ docker tag
docker tag myapp:latest myrepo/myapp:1.0

Tag an image with a new name or version.

$ docker rmi
docker rmi myapp:old

Remove one or more local images.

# containers
$ docker run
docker run -d -p 8080:80 nginx

Create and start a new container from an image.

$ docker ps
docker ps -a

List containers β€” add -a to include stopped ones.

$ docker stop
docker stop web

Gracefully stop a running container.

$ docker start
docker start web

Start a previously stopped container.

$ docker restart
docker restart web

Restart a container.

$ docker rm
docker rm web

Remove a stopped container.

# logs, inspect & debug
$ docker exec -it
docker exec -it web sh

Run a command inside a running container, e.g. an interactive shell.

$ docker logs -f
docker logs -f web

Follow a container's log output in real time.

$ docker inspect
docker inspect web

Show detailed low-level info about a container or image.

$ docker top
docker top web

Show the running processes inside a container.

$ docker stats
docker stats

Live stream of CPU/memory/network usage per container.

$ docker cp
docker cp web:/app/log.txt .

Copy files between a container and the host.

# volumes & networks
$ docker volume ls
docker volume ls

List Docker-managed volumes.

$ docker network ls
docker network ls

List Docker networks.

$ docker network inspect
docker network inspect bridge

Show a network's config and connected containers.

# compose & cleanup
$ docker compose up
docker compose up -d

Start all services defined in docker-compose.yml, detached.

$ docker compose down
docker compose down

Stop and remove containers/networks created by Compose.

$ docker compose logs
docker compose logs -f

Follow log output from all Compose services.

$ docker system prune
docker system prune -a

Remove unused containers, images, and networks to free space.

git.sh
$ git --help

🌿 Git Essentials_

24 commands covering the daily Git workflow β€” setup, the local commit cycle, branching and merging, working with remotes, inspecting history, and undoing mistakes safely.

# setup & config
$ git init
git init

Turn the current directory into a new Git repository.

$ git clone
git clone [email protected]:user/repo.git

Download a full copy of a remote repository, including its history.

$ git config --global
git config --global user.name "Suleman"

Set identity/preferences that apply across all repos for this user.

$ git config --list
git config --list

Show all effective Git configuration values currently in use.

# daily workflow
$ git status
git status

Show staged, unstaged, and untracked changes in the working directory.

$ git add
git add .

Stage file changes to be included in the next commit.

$ git commit -m
git commit -m "fix: handle null response"

Save staged changes to history with a descriptive message.

$ git diff
git diff --staged

Show line-by-line changes not yet committed (add --staged for staged ones).

# branching & merging
$ git branch
git branch -a

List local (and with -a, remote-tracking) branches.

$ git switch -c
git switch -c feature/login

Create and switch to a new branch in one step.

$ git merge
git merge feature/login

Integrate another branch's commits into the current branch.

$ git rebase
git rebase main

Replay current branch commits on top of another branch for a linear history.

$ git branch -d
git branch -d feature/login

Delete a local branch once it's merged.

# remotes & sync
$ git remote -v
git remote -v

Show the remote repositories this repo is linked to.

$ git push
git push -u origin feature/login

Upload local commits to a remote branch (-u sets tracking for future pushes).

$ git pull
git pull --rebase

Fetch and integrate remote changes into the current branch.

$ git fetch
git fetch origin

Download remote history without merging it into your working branch.

# inspecting history
$ git log --oneline
git log --oneline --graph --all

Compact, visual view of commit history across all branches.

$ git show
git show a1b2c3d

Show the full diff and metadata for a single commit.

$ git blame
git blame app.py

Show which commit last changed each line of a file.

# undoing & cleanup
$ git restore
git restore --staged app.py

Unstage a file, or discard unstaged local edits back to the last commit.

$ git reset --hard
git reset --hard HEAD~1

Move the branch pointer back a commit, discarding changes since then.

$ git revert
git revert a1b2c3d

Create a new commit that undoes a previous one β€” safe on shared history.

$ git stash
git stash pop

Temporarily shelve uncommitted changes, then reapply them later.

k8s.sh
$ kubectl cheatsheet

☸️ Kubernetes Essentials_

25 kubectl commands for day-to-day cluster work β€” context switching, workloads, deployments and scaling, services and networking, debugging, and namespaces/secrets.

# cluster & context
$ cluster-info
kubectl cluster-info

Show the cluster's control-plane and service endpoints.

$ config get-contexts
kubectl config get-contexts

List available kubeconfig contexts.

$ config use-context
kubectl config use-context prod

Switch the active cluster context.

$ version
kubectl version --short

Show client and server Kubernetes versions.

# workloads
$ get pods
kubectl get pods -o wide

List pods in the current namespace.

$ describe pod
kubectl describe pod web-abc123

Show detailed info and recent events for a pod.

$ logs -f
kubectl logs -f web-abc123

Stream logs from a pod or container.

$ exec -it
kubectl exec -it web-abc123 -- sh

Run a command inside a running pod.

$ apply -f
kubectl apply -f deployment.yaml

Create or update resources from a manifest file.

$ delete -f
kubectl delete -f deployment.yaml

Delete resources defined in a manifest file.

$ rollout status
kubectl rollout status deployment/web

Watch the progress of a deployment rollout.

# deployments & scaling
$ get deployments
kubectl get deployments

List deployments and their replica status.

$ scale
kubectl scale deployment/web --replicas=5

Change the replica count of a deployment.

$ rollout undo
kubectl rollout undo deployment/web

Roll a deployment back to its previous revision.

$ set image
kubectl set image deployment/web app=web:2.0

Update a deployment's container image and trigger a rollout.

# services & networking
$ get svc
kubectl get svc

List services and their cluster/external IPs.

$ port-forward
kubectl port-forward svc/web 8080:80

Forward a local port to a pod or service.

$ get ingress
kubectl get ingress

List ingress rules and the hosts they route.

# config & debug
$ get nodes
kubectl get nodes -o wide

List cluster nodes and their status.

$ top pod
kubectl top pod

Show live CPU/memory usage per pod (needs metrics-server).

$ get events
kubectl get events --sort-by=.lastTimestamp

Show recent cluster events, most recent last.

$ edit
kubectl edit deployment/web

Open a live resource in your editor to modify it directly.

# namespaces & secrets
$ get namespaces
kubectl get ns

List all namespaces in the cluster.

$ create secret
kubectl create secret generic db-pass --from-literal=password=xyz

Create a Secret from literal values or files.

$ get configmap
kubectl get configmap

List ConfigMaps in the current namespace.