Skip to content

DevOps

Deploy a Production VPN on AWS in Under 10 Minutes with Terraform

TL;DR: I built a Terraform module that provisions a fully configured OpenVPN or WireGuard server on AWS -- complete with certificates, firewall rules, and client configs -- in a single terraform apply.

The Problem

Setting up a VPN sounds simple until you actually do it. You need an EC2 instance, security groups, elastic IP, key pairs, DNS records, and then the VPN software itself -- certificates for OpenVPN or key pairs for WireGuard. Most tutorials walk you through 30+ manual steps, and the result is a snowflake server that nobody can reproduce. When the instance dies, you start from scratch. I wanted a VPN I could destroy and recreate in minutes.

What I Built

aws-vpn-provisioner is a Terraform project that automates the entire VPN deployment lifecycle on AWS. Choose OpenVPN or WireGuard, set a few variables, and Terraform handles everything from infrastructure to software configuration via cloud-init user data scripts.

  • Dual protocol support -- deploy OpenVPN (with Easy-RSA PKI) or WireGuard (with auto-generated peer keys) using the same Terraform codebase
  • Full infrastructure as code -- VPC (or existing VPC), subnet, security groups, EC2 instance, Elastic IP, and Route53 DNS record
  • Automated server configuration -- cloud-init scripts install and configure the VPN software on first boot, no SSH required
  • Client config generation -- .ovpn files for OpenVPN or WireGuard conf files are generated and stored in S3 for secure retrieval
  • Cost-conscious defaults -- uses t3.micro with spot pricing by default, keeping monthly costs under $5
  • Tear down and rebuild -- terraform destroy cleans up everything; terraform apply rebuilds identically

Tech Stack

Terraform, AWS (EC2, VPC, S3, Route53, IAM), OpenVPN, WireGuard, cloud-init, Bash

Usage

# Clone and configure
git clone https://github.com/basel5001/aws-vpn-provisioner.git
cd aws-vpn-provisioner

# Deploy WireGuard
terraform init
terraform apply -var="vpn_type=wireguard" -var="domain=vpn.example.com"

# Download client config from S3
aws s3 cp s3://vpn-configs-bucket/client.conf ./wireguard-client.conf

Results

Total deployment time from clone to connected VPN: under 8 minutes. Destroying and redeploying takes 4 minutes. Monthly cost with a t3.micro spot instance sits around $3.50. I have used this to spin up temporary VPN servers for travel, tear them down on return, and avoid paying for idle infrastructure. The WireGuard option consistently delivers 400+ Mbps throughput -- more than enough for any remote work scenario.


GitHub: https://github.com/basel5001/aws-vpn-provisioner

An Interactive Bash Script for Linux User and Group Management

TL;DR: I built an interactive bash script that handles the most common Linux user and group administration tasks -- creating users, managing groups, setting permissions, and bulk operations -- through a menu-driven interface.

The Problem

Linux user management is straightforward if you have the commands memorized. But for junior admins, students, or anyone who does not manage Linux systems daily, remembering the exact syntax for useradd, usermod, groupadd, passwd, and their various flags is a constant trip to the man pages. One wrong flag and you have locked out a user or assigned the wrong shell. I wanted a tool that makes these operations safe and accessible while still being educational about what is happening under the hood.

What I Built

bash-scripting is an interactive menu-driven bash script for managing Linux users and groups. It wraps the standard Linux user management commands in a guided interface that validates input, confirms destructive actions, and logs everything it does.

  • User creation -- creates users with configurable home directories, shells, and group memberships. Validates that usernames follow naming conventions and prompts for password setup
  • User modification -- change shells, lock/unlock accounts, update group memberships, set password expiration policies, and modify home directories
  • User deletion -- removes users with optional home directory cleanup, confirms before executing, and logs the removal
  • Group management -- create, delete, and rename groups. Add or remove users from groups interactively
  • Bulk operations -- import a CSV of users and create them all in one run, or export the current user list to CSV for auditing
  • Password management -- force password resets, set expiration policies, and check password status across users
  • Audit logging -- every action is logged to /var/log/user-management.log with timestamps and the admin who ran it
  • Dry-run mode -- preview exactly what commands will execute before running them

Tech Stack

Bash, Linux (useradd, usermod, groupadd, passwd, chage), AWK, CSV parsing

Usage

# Run interactively
sudo ./user-manager.sh

# Bulk create users from CSV
sudo ./user-manager.sh --bulk-create users.csv

# Dry run
sudo ./user-manager.sh --dry-run

# CSV format: username,fullname,shell,groups
# john.doe,John Doe,/bin/bash,developers;sudo

Results

This script has been used as a teaching tool in a Linux administration course where students practice user management without risking misconfigured commands. The dry-run mode catches roughly one mistake per session that would have required manual cleanup. Bulk user creation from CSV reduced onboarding time for a 15-person lab setup from 45 minutes of manual work to under 2 minutes. The audit log has proven useful for tracking who made what changes during group lab exercises.


GitHub: https://github.com/basel5001/Bash-Scripting

A Kubernetes Chaos Engineering Toolkit with AI-Powered Analysis

TL;DR: I built a chaos engineering toolkit that runs five experiment types against Kubernetes clusters and uses AWS Bedrock to analyze the results and recommend resilience improvements.

The Problem

"Our system is resilient" is a claim, not a fact, until you've tested it. Most teams discover their failure modes during actual incidents -- the worst possible time. I needed a way to systematically inject failures into Kubernetes workloads, observe how the system responds, and get actionable recommendations, not just pass/fail results.

What I Built

chaos-engineering is a CLI-driven toolkit that defines experiments as YAML manifests (CRD-style), executes them against a target Kubernetes cluster, collects metrics during the blast radius, and feeds everything into AWS Bedrock for AI-powered analysis. The AI doesn't just say "the pod restarted" -- it explains whether the recovery was fast enough, what the impact radius was, and what to harden next.

  • 5 experiment types -- pod kill (random pod termination), network latency (tc netem injection), CPU stress (stress-ng on target nodes), disk fill (hostPath exhaustion), and DNS failure (CoreDNS SERVFAIL injection)
  • Declarative experiment plans -- define target namespace, label selectors, duration, and success criteria in YAML
  • AI-powered analysis -- Bedrock correlates experiment timeline with cluster metrics and provides a risk assessment with specific remediation steps
  • Dry-run mode -- validate experiments and see what would be affected without injecting any faults
  • HTML results dashboard -- a self-contained visualization of experiment timelines, impact metrics, and AI recommendations

Tech Stack

Python, Kubernetes (kubectl), AWS Bedrock, Docker, stress-ng, tc/netem, Terraform, GitHub Actions

Usage

# manifests/experiment-pod-kill.yml
apiVersion: chaos.xops.io/v1alpha1
kind: ChaosExperiment
metadata:
  name: kill-frontend-pods
spec:
  type: pod-kill
  target:
    namespace: production
    labelSelector:
      matchLabels:
        app: frontend
  schedule:
    duration: 60s
    count: 3
# Dry-run to preview impact
chaos --manifest manifests/experiment-pod-kill.yml --dry-run

# Execute with AI analysis
chaos --manifest manifests/experiment-pod-kill.yml --analyze

# Export results
chaos --manifest manifests/experiment-network.yml --output json --outfile results.json

Results

Running weekly chaos experiments surfaced three critical resilience gaps: a service without a PodDisruptionBudget that went fully unavailable during pod kills, a DNS-dependent service with no caching that cascaded failures during DNS experiments, and a CPU-bound workload with no horizontal autoscaler. Each was caught and fixed before a real incident.


GitHub: https://github.com/basel5001/chaos-engineering

A GitHub Action That Catches Technical Debt Before It Merges

TL;DR: I built a GitHub Action that scans pull requests for technical debt patterns -- TODOs, FIXMEs, empty catch blocks, hardcoded secrets, and oversized files -- and blocks the merge until they are addressed.

The Problem

Technical debt accumulates silently. A developer drops a TODO: fix this later comment and moves on. Someone writes an empty catch block during a quick prototype. A hardcoded API key sneaks into a config file. Individually, none of these are catastrophic. Collectively, they erode codebase quality over months until the team is spending more time fighting the code than building features. Code reviews catch some of it, but reviewers are human -- they miss things, especially under time pressure.

What I Built

debtless is a GitHub Action that runs on every pull request and automatically scans the diff for common technical debt patterns. It annotates the PR with inline comments pointing to each finding and can be configured to block the merge if the debt exceeds a threshold.

  • Pattern detection -- scans for TODO, FIXME, HACK, XXX, WORKAROUND, and custom markers
  • Empty catch blocks -- detects catch blocks with no error handling across JavaScript, TypeScript, Python, Java, and Go
  • Hardcoded secrets -- flags patterns that look like API keys, tokens, passwords, and connection strings
  • Large file detection -- warns when files exceed a configurable line count, catching monolith classes before they grow
  • Inline PR annotations -- findings appear as review comments directly on the changed lines, not buried in logs
  • Configurable thresholds -- set a maximum debt score per PR; exceed it and the check fails
  • Trend tracking -- outputs a debt score over time so you can measure whether the codebase is getting better or worse

Tech Stack

GitHub Actions, TypeScript, GitHub Checks API, Regular Expressions, YAML Configuration

Usage

# .github/workflows/debtless.yml
name: Debt Check
on: [pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: basel5001/debtless@main
        with:
          max-debt-score: 10
          fail-on-secrets: true
          ignore-paths: "vendor/,node_modules/"

Results

After enabling debtless on three active repositories, the team resolved 47 TODO comments and 8 empty catch blocks within the first two weeks -- not because anyone mandated a cleanup sprint, but because the action surfaced them during normal PR reviews. The hardcoded-secrets check caught two AWS access keys that had been committed in test fixtures. Debt score trending showed a 30% reduction in new debt items per sprint after the first month.


GitHub: https://github.com/basel5001/debtless

Measuring What Matters: A DORA Metrics Dashboard Built on the GitHub API

TL;DR: I built a FastAPI dashboard that pulls deployment data from the GitHub API and calculates the four DORA metrics in real time -- giving engineering teams actual numbers instead of gut feelings about delivery performance.

The Problem

Every engineering manager has been asked "how fast can we ship?" and answered with a vague "pretty fast." DORA metrics -- deployment frequency, lead time for changes, mean time to recovery (MTTR), and change failure rate -- are the industry standard for measuring software delivery performance. But most teams either pay for expensive platforms or simply never measure. I wanted a self-hosted, open-source alternative that pulls directly from where the work already happens: GitHub.

What I Built

devops-dashboard is a Python application that connects to the GitHub API, ingests deployment and pull request data, and computes all four DORA metrics with historical trends. A clean web dashboard presents the numbers alongside DORA's Elite/High/Medium/Low benchmarks so teams can see exactly where they stand.

  • Deployment Frequency -- calculated from GitHub Deployments or tagged releases, shown as daily/weekly/monthly rates
  • Lead Time for Changes -- measured from first commit on a branch to production deployment, broken down by percentile
  • Mean Time to Recovery -- tracked by correlating incident-labeled issues with the deployment that resolved them
  • Change Failure Rate -- ratio of deployments followed by a rollback or hotfix within a configurable window
  • Trend charts -- week-over-week sparklines for each metric so you can spot regressions early
  • Multi-repo aggregation -- track metrics across your entire organization, not just one repo

Tech Stack

Python, FastAPI, GitHub REST API, SQLite, Jinja2, Chart.js, Docker

Usage

# Clone and start
git clone https://github.com/basel5001/devops-dashboard.git
cd devops-dashboard
cp .env.example .env  # add GITHUB_TOKEN
docker compose up -d
# Open http://localhost:8000

Results

Within the first sprint of running the dashboard, the team discovered that lead time had ballooned to 4.2 days -- well above the "Elite" threshold of under one day. The root cause turned out to be a stale review policy requiring two approvals on trivial changes. After adjusting the policy, lead time dropped to 1.1 days. Without the numbers, that bottleneck would have stayed invisible.


GitHub: https://github.com/basel5001/devops-dashboard

One GitHub Action to Run Four Security Scanners and an AI Risk Analyst

TL;DR: I packaged Gitleaks, Trivy, Semgrep, and Checkov into a single reusable GitHub Action with AI-powered risk analysis and a self-contained HTML dashboard.

The Problem

Security scanning in most CI pipelines is bolted on as an afterthought -- a separate workflow per tool, each with its own output format, its own failure threshold, and its own results that nobody reads. Developers get alert fatigue, and real vulnerabilities hide in the noise. I wanted a single step that runs everything, normalizes the results, and tells me what actually matters.

What I Built

devsecops-pipeline is a comprehensive security scanning platform that orchestrates four industry-standard scanners in parallel, feeds the combined results into AWS Bedrock for AI risk analysis, and renders everything into a self-contained HTML dashboard. It ships as a reusable GitHub Action, a Docker image, and a standalone CLI.

  • 4 scanners in one step -- Gitleaks (secrets), Trivy (CVEs in dependencies and containers), Semgrep (SAST/code patterns), and Checkov (Terraform/CloudFormation misconfigurations)
  • AI risk analysis -- AWS Bedrock analyzes combined findings, prioritizes by actual exploitability, and generates remediation guidance
  • Self-contained HTML dashboard -- a single file with no external dependencies; drop it into any PR comment or artifact
  • Configurable fail thresholds -- fail the build on critical findings only, or set custom severity gates
  • Deployable to AWS -- Terraform module provisions Lambda + S3 + SNS for scheduled organization-wide scans

Tech Stack

Python, GitHub Actions, Docker, Gitleaks, Trivy, Semgrep, Checkov, AWS Bedrock, Terraform, HTML/CSS

Usage

# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: basel5001/devsecops-pipeline@v1
        with:
          path: '.'
          fail-on-critical: 'true'
          ai-analysis: 'true'

Or scan locally with Docker:

SCAN_PATH=/path/to/repo docker compose up --build
# Results appear in ./reports/

Results

Four tools, one workflow step, one dashboard. The AI analysis cut triage time by roughly 60% -- instead of scrolling through 200 raw findings, developers get a prioritized list with context. Critical secrets and high-severity CVEs block the merge; informational findings surface without breaking flow.


GitHub: https://github.com/basel5001/devsecops-pipeline

Automating AWS Disaster Recovery with AI-Generated Runbooks

TL;DR: I built a Terraform-managed AWS disaster recovery system with cross-region replication, automated failover, scheduled DR drills that measure RTO/RPO, and AI-generated runbooks via Bedrock.

The Problem

Every team says they have a DR plan. Few have tested it. Fewer still can tell you their actual RTO and RPO numbers. Manual failover runbooks go stale the moment the infrastructure changes. I needed DR that tests itself, measures itself, and keeps its own documentation current.

What I Built

disaster-recovery-aws is a complete DR automation platform spanning two AWS regions. The primary region (us-east-1) replicates data to a DR region (us-west-2) across S3, RDS, and DynamoDB. Lambda functions continuously monitor health, execute failover when thresholds breach, and run scheduled DR drills that produce hard RTO/RPO measurements.

  • Cross-region replication -- S3 CRR, RDS read replicas with automatic promotion, DynamoDB global tables
  • Automated failover -- Lambda detects health check failures and executes DNS switchover, RDS promotion, and ECS scaling without human intervention
  • Scheduled DR drills -- EventBridge triggers simulated failures; each drill measures actual recovery time and data loss, then reports via SNS
  • AI runbook generation -- AWS Bedrock generates step-by-step runbooks tailored to the current infrastructure state, not a static wiki page
  • Full observability -- CloudWatch alarms, a unified dashboard, and Synthetics canaries for external endpoint monitoring

Tech Stack

Terraform, Python, AWS (Lambda, RDS, S3, DynamoDB, Route53, ECS, ALB, CloudWatch, EventBridge, SNS, Bedrock)

Usage

# Deploy the full DR stack
cd terraform
cp backend.tf.example backend.tf
terraform init && terraform plan

# Run a manual DR drill
aws lambda invoke --function-name dr-drill-runner \
  --payload '{"scenario": "rds-failover"}' response.json

# Generate a runbook for a specific scenario
aws lambda invoke --function-name dr-runbook-generator \
  --payload '{"scenario": "full-region-failure"}' runbook.json

Results

DR drills now run weekly on a schedule. The first automated drill revealed an RTO of 12 minutes -- twice what we assumed. After tuning, we consistently hit under 5 minutes. AI-generated runbooks stay current because they query live infrastructure state each time. The biggest win: confidence. When an actual us-east-1 degradation occurred, the system detected it and began failover within 90 seconds.


GitHub: https://github.com/basel5001/disaster-recovery-aws

How I Centralized CI/CD Governance Across 22+ Repos with a Single Repository

TL;DR: I built a single "infrastructure" repo that owns every reusable GitHub Actions workflow, composite action, and repository setting across my entire GitHub organization.

The Problem

Managing CI/CD pipelines across dozens of repositories is painful. Every repo had its own copy of linting configs, security scans, and Docker build steps. A single change -- like upgrading a scanner version -- meant opening 20+ PRs. Drift was inevitable, and nobody could answer "which repos actually run secret scanning?"

What I Built

githubinfra is a mono-repo that treats GitHub itself as infrastructure. Every workflow is a reusable workflow that any repo can reference with a one-liner. Repository settings (branch protection, merge strategies, topics, descriptions) are declared in a single repos.yml and synced via a TypeScript management tool.

  • 11 reusable workflows covering CI, linting, secret scanning, IOC/supply-chain checks, Docker builds, Terraform validation, SBOM generation, stale management, release notifications, branch copying, and label enforcement
  • 3 composite actions for environment setup (Python/Node/Go with caching), Docker Buildx builds, and Discord/Slack notifications
  • Declarative repo management -- edit repos.yml, run npm run sync, and every repo's settings converge to the desired state
  • Diff & dry-run modes so I can preview exactly what will change before applying
  • Automated workflow assignment -- new repos inherit the right pipelines from day one

Tech Stack

GitHub Actions, TypeScript, Terraform, GitHub REST API, YAML

Usage

Any repo references a shared workflow with two lines:

jobs:
  ci:
    uses: basel5001/githubinfra/.github/workflows/ci.yml@main
    with:
      language: python

To sync repository settings:

cd management && npm install
GITHUB_TOKEN=ghp_xxx npm run diff    # preview
GITHUB_TOKEN=ghp_xxx npm run sync    # apply

Results

What used to require touching 20+ repos now takes a single PR. Workflow updates roll out instantly to every consumer. The repos.yml file serves as a living inventory of the entire organization -- I can answer "what scanning does repo X run?" by reading one file. Onboarding a new repo takes under a minute: add it to repos.yml, push, done.


GitHub: https://github.com/basel5001/githubinfra

Building a Full GitOps Platform with Progressive Delivery

TL;DR: I built a production-grade GitOps platform on Kubernetes using ArgoCD, Crossplane, Kyverno, and Argo Rollouts -- enabling multi-environment promotion with policy enforcement and canary/blue-green deployments.

The Problem

Kubernetes gives you primitives, not a platform. After the cluster is running, you still need to answer: How do deployments promote from dev to prod? Who enforces that every pod has resource limits? How do you provision cloud resources without leaving the Git workflow? I needed a complete platform, not a collection of tools.

What I Built

gitops-cluster is a fully integrated GitOps platform where Git is the single source of truth for both application manifests and infrastructure. ArgoCD syncs the desired state, Crossplane provisions cloud resources as Kubernetes CRDs, Kyverno enforces policies at admission time, and Argo Rollouts handles progressive delivery with canary and blue-green strategies.

  • Multi-environment promotion -- dev uses canary rollouts (automatic), staging uses blue-green (auto-promote), prod uses blue-green (manual gate)
  • Infrastructure as CRDs -- Crossplane compositions provision S3 buckets, RDS instances, and IAM roles via kubectl apply
  • Policy enforcement -- Kyverno blocks deployments without resource limits, requires labels, enforces image pull policies, and restricts privilege escalation
  • App-of-Apps pattern -- a single ArgoCD Application bootstraps the entire platform; adding a new service is a YAML file in the right directory
  • Local development -- kind create cluster && make bootstrap gives you the full platform locally

Tech Stack

Kubernetes, ArgoCD, Crossplane, Kyverno, Argo Rollouts, Terraform (EKS bootstrap), Helm, Kustomize

Usage

# Local: bootstrap a kind cluster with the full platform
kind create cluster --name gitops-cluster
make bootstrap

# AWS: provision EKS, then bootstrap
cd terraform && terraform apply
make bootstrap-eks

# Deploy an app: just add a manifest and push
cat > apps/dev/my-service.yml <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-service
spec:
  strategy:
    canary:
      steps:
        - setWeight: 20
        - pause: {duration: 30s}
        - setWeight: 50
        - pause: {duration: 30s}
EOF
git add . && git commit -m "deploy my-service" && git push

Results

Deployments flow from dev to prod through a Git-driven pipeline with automatic canary analysis. Kyverno has blocked over 40 non-compliant deployments in staging before they reached production. Crossplane eliminated the context switch between "I need a Kubernetes deployment" and "I need an RDS database" -- both are just YAML in the same repo.


GitHub: https://github.com/basel5001/gitops-cluster

5 Free Ways to Expose Your Local Server to the Internet

TL;DR: I documented and automated five free methods to tunnel local services to the public internet -- Cloudflare Tunnel, DuckDNS + Let's Encrypt, WireGuard, FRP, Rathole, and SSH -- so you never pay for ngrok again.

The Problem

You are building a webhook integration and Stripe needs to reach your local development server. Or you are self-hosting a home lab service and want to access it remotely. Or you are demoing an app to a client and need a public URL in 30 seconds. The go-to solution is ngrok, but the free tier is limited and the paid plans add up. There are at least five completely free alternatives, but each has different tradeoffs and setup complexity. I wanted a single repo that documents and automates all of them.

What I Built

local-tunnel-server is a reference repository with configuration files, scripts, and step-by-step guides for five different tunneling methods. Each method includes a working setup, a one-command deploy script, and honest documentation of its strengths and limitations.

  • Cloudflare Tunnel -- the easiest option; install cloudflared, run one command, get a public HTTPS URL backed by Cloudflare's network. Zero port forwarding, free custom domains with Cloudflare DNS
  • DuckDNS + Let's Encrypt -- free dynamic DNS with automated TLS certificates. Best for persistent home lab services that need a stable hostname
  • WireGuard VPN + Reverse Proxy -- tunnel traffic through a cheap VPS running WireGuard. Gives you a static IP and full control over routing
  • FRP (Fast Reverse Proxy) -- open-source reverse proxy with a lightweight server/client architecture. Supports TCP, UDP, HTTP, and HTTPS tunnels with dashboard monitoring
  • Rathole -- a Rust-based alternative to FRP with lower resource usage. Ideal for constrained environments like Raspberry Pi
  • SSH Reverse Tunnel -- the classic approach; requires only an SSH server. No additional software, works everywhere, but lacks automatic reconnection without tooling

Tech Stack

Cloudflare Tunnel, WireGuard, FRP, Rathole, DuckDNS, Let's Encrypt, SSH, Nginx, Docker, Bash

Usage

# Cloudflare Tunnel (fastest setup)
cloudflared tunnel --url http://localhost:3000

# FRP (self-hosted)
docker compose -f frp/docker-compose.yml up -d

# WireGuard + reverse proxy
./scripts/setup-wireguard.sh
./scripts/setup-nginx-proxy.sh

Results

Cloudflare Tunnel is the winner for quick development tunnels -- zero config, free HTTPS, and sub-100ms latency overhead. For permanent home lab exposure, WireGuard + a $3.50/month VPS provides the most flexibility and performance. FRP hits the sweet spot for teams that want a self-hosted ngrok alternative with a management dashboard. This repo has become my go-to reference every time I need to expose a local service.


GitHub: https://github.com/basel5001/local-tunnel-server