DevOps Engineering Portfolio

Live

Three sanitized case studies from production work at Keel Digital. Each documents the actual constraints and the reasoning behind the architecture that answered them, with before/after diagrams rather than just the finished state. Hostnames, network ranges, and service names are generalized; the architecture and decisions are real. The full write-ups live on GitHub.

Proxmox bare-metal migration

The dev environments ran on 14 DigitalOcean droplets, each billing independently and each with its own public IP — which meant 14 internet-facing machines that needed individual firewall hygiene, patching urgency, and monitoring as if they were production, for what were functionally internal environments.

The replacement is a single OVHcloud bare-metal server running Proxmox VE with two bridges: vmbr0 carries the public interface and connects only to a minimal gateway VM; vmbr1 is an internal RFC1918 bridge where every dev VM lives, with no route out except through the gateway. The gateway masquerades outbound traffic via nftables, and inbound access is deliberately narrow — SSH to the gateway, then onward, with no direct port-forwards to dev VMs by default. Because all egress now passes one chokepoint, logging and restricting what dev environments can reach externally became a single config file instead of 14.

The migration favored rebuild over copy: services already deployed via configuration were rebuilt fresh — which doubled as a test that the environment setup was actually reproducible — while databases were synced ahead of time and re-synced during cutover. New environments ran in parallel with the droplets, and nothing was destroyed until its replacement was confirmed working. Results: ~$1,500 CAD/month eliminated, attack surface down from 14 public IPs to 1, dedicated CPU and NVMe in place of shared-vCPU droplets, and Proxmox snapshot/restore where backups had been per-droplet and ad hoc. The retrospective is candid: the rebuild-vs-migrate classification took longer than the migration itself, and internal DNS should have existed before the first VM moved. Read the case study

Self-managed OpenSearch SIEM

A HIPAA environment with FedRAMP in scope, and observability scattered across CloudWatch, Keycloak, and Zabbix with no way to correlate between them. Three constraints shaped the build: logs containing PHI-adjacent metadata couldn't leave the AWS account in transit to a third-party SIEM, putting managed SaaS options off the table without significant legal review; per-GB commercial licensing gets expensive fast at 40+ services of continuous log volume; and detections needed to be owned and tuned in-house, not fit into a vendor's fixed rule taxonomy. Self-managed OpenSearch on AWS answered all three.

Ingestion runs Fluent Bit as a sidecar on every ECS task into a central Logstash cluster, which normalizes fields, enriches at write time (IP geolocation, instance metadata, Keycloak realm context — cheaper once on write than repeatedly at query), and routes log types to index patterns with different retention. ISM policies tier the lifecycle: hot on SSD for 7 days, warm and compressed for 30, then snapshots to encrypted S3 for the one-year retention HIPAA requires. Detections are Sigma rules compiled to OpenSearch DSL with sigma-cli and tracked in git with normal PR review — the audit trail for "when did we start detecting X" is just git log. Alerts route to Slack for low severity and PagerDuty for high.

The build order mattered: schema and index templates before any data (remapping after indexing means an expensive reindex), sources one at a time in order of compliance value starting with CloudTrail, detections tuned against real data before any dashboards. Auditor requests like "all authentication events for user X over 90 days" went from a manual process to a saved search. The lessons-learned section covers a user_id field collision that forced a mid-stream reindex, and an initial detection set noisy enough that tuning took longer than writing the rules. Read the case study

OPKSSH dev access

Dev servers had three shared Linux accounts — devreadonly, devuser, devadmin — and access meant a sysadmin manually adding your public key to the right authorized_keys file. Static keys never expired, offboarding depended on a checklist being followed on every server, and answering "who has what access" meant matching key fingerprints across machines by hand.

OPKSSH replaces the static key with an SSH certificate cryptographically bound to a Google Workspace identity. A developer runs opkssh login, completes a browser OAuth flow, and receives a time-limited cert carrying their email and Google Group claims. Server-side, an AuthorizedPrincipalsCommand in sshd_config invokes the OPKSSH verifier, which checks the cert against Google's JWKS, confirms the token hasn't expired, and matches group claims against /etc/opkssh/policy.yml — a per-server mapping of Google Groups to the three Linux accounts. No authorized_keys file involved. The account tiers themselves were kept as-is; only what authorizes a person to use each one changed.

The migration ran both auth paths in parallel: a fingerprint audit mapped every existing key to a person (surfacing two keys attributable to nobody current), Google Groups were populated to mirror the existing state, and developers moved over in small batches before authorized_keys was removed entirely. Offboarding was verified directly — a test account was suspended in Workspace and a freshly issued cert for that identity was rejected at OIDC validation. Access changes are now a Google Group edit, a leaked cert is self-revoking at token expiry, and the orphaned-key problem is structurally impossible. Read the case study