Add secure portfolio foundation

This commit is contained in:
2026-07-16 05:16:02 +00:00
parent 155c31b8a2
commit be79a1e9f3
14 changed files with 1034 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: portfolio
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
app.kubernetes.io/component: web
app.kubernetes.io/part-of: portfolio-platform
spec:
replicas: 2
revisionHistoryLimit: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
template:
metadata:
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
app.kubernetes.io/component: web
app.kubernetes.io/part-of: portfolio-platform
spec:
serviceAccountName: portfolio
automountServiceAccountToken: false
enableServiceLinks: false
terminationGracePeriodSeconds: 20
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
fsGroup: 101
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
containers:
- name: portfolio
image: nginxinc/nginx-unprivileged:1.29.4-alpine
imagePullPolicy: IfNotPresent
command:
- nginx
args:
- -g
- daemon off;
ports:
- name: http
containerPort: 8080
protocol: TCP
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: 25m
memory: 32Mi
ephemeral-storage: 16Mi
limits:
cpu: 250m
memory: 128Mi
ephemeral-storage: 128Mi
startupProbe:
httpGet:
path: /healthz
port: http
failureThreshold: 30
periodSeconds: 2
timeoutSeconds: 2
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 2
failureThreshold: 3
volumeMounts:
- name: site
mountPath: /usr/share/nginx/html
readOnly: true
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: site
configMap:
name: portfolio-site
defaultMode: 0444
- name: nginx-config
configMap:
name: portfolio-nginx
defaultMode: 0444
- name: tmp
emptyDir:
medium: Memory
sizeLimit: 32Mi
+34
View File
@@ -0,0 +1,34 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: portfolio
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
app.kubernetes.io/component: web
app.kubernetes.io/part-of: portfolio-platform
annotations:
cert-manager.io/cluster-issuer: home-lab-ca
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "1m"
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-burst-multiplier: "5"
spec:
ingressClassName: nginx
tls:
- hosts:
- portfolio.home.lan
secretName: portfolio-home-lan-tls
rules:
- host: portfolio.home.lan
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: portfolio
port:
name: http
+27
View File
@@ -0,0 +1,27 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: portfolio
resources:
- serviceaccount.yaml
- resourcequota.yaml
- limitrange.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
- networkpolicy.yaml
- poddisruptionbudget.yaml
configMapGenerator:
- name: portfolio-site
files:
- index.html=site/index.html
- name: portfolio-nginx
files:
- default.conf=nginx/default.conf
generatorOptions:
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/part-of: portfolio-platform
+26
View File
@@ -0,0 +1,26 @@
apiVersion: v1
kind: LimitRange
metadata:
name: portfolio-limits
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/part-of: portfolio-platform
spec:
limits:
- type: Container
defaultRequest:
cpu: 25m
memory: 32Mi
ephemeral-storage: 16Mi
default:
cpu: 250m
memory: 128Mi
ephemeral-storage: 128Mi
min:
cpu: 5m
memory: 16Mi
max:
cpu: "1"
memory: 512Mi
ephemeral-storage: 512Mi
+41
View File
@@ -0,0 +1,41 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/part-of: portfolio-platform
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-nginx
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/part-of: portfolio-platform
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/component: controller
ports:
- protocol: TCP
port: 8080
+28
View File
@@ -0,0 +1,28 @@
server {
listen 8080 default_server;
listen [::]:8080 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
server_tokens off;
access_log /dev/stdout;
error_log /dev/stderr warn;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
location / {
try_files $uri $uri/ /index.html;
}
}
+15
View File
@@ -0,0 +1,15 @@
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: portfolio
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
app.kubernetes.io/part-of: portfolio-platform
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
+22
View File
@@ -0,0 +1,22 @@
apiVersion: v1
kind: ResourceQuota
metadata:
name: portfolio-quota
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/part-of: portfolio-platform
spec:
hard:
requests.cpu: "500m"
requests.memory: 512Mi
limits.cpu: "2"
limits.memory: 1Gi
requests.ephemeral-storage: 256Mi
limits.ephemeral-storage: 1Gi
pods: "6"
services: "4"
configmaps: "10"
count/deployments.apps: "3"
count/ingresses.networking.k8s.io: "2"
count/networkpolicies.networking.k8s.io: "10"
+20
View File
@@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: portfolio
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
app.kubernetes.io/component: web
app.kubernetes.io/part-of: portfolio-platform
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: portfolio
namespace: portfolio
labels:
app.kubernetes.io/name: portfolio
app.kubernetes.io/instance: portfolio
app.kubernetes.io/component: web
app.kubernetes.io/part-of: portfolio-platform
automountServiceAccountToken: false
+599
View File
@@ -0,0 +1,599 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Jay Phillips — Systems Administrator and Infrastructure Engineer specializing in Kubernetes, Windows Server, VMware, automation, GitOps, and observability.">
<title>Jay Phillips | Infrastructure Portfolio</title>
<style>
:root {
color-scheme: dark;
--background: #07111f;
--surface: rgba(15, 31, 51, 0.82);
--surface-strong: #10243a;
--border: rgba(148, 184, 219, 0.2);
--text: #eef7ff;
--muted: #a7bed2;
--accent: #65d4ff;
--accent-strong: #79f2c0;
--shadow: 0 24px 70px rgba(0, 0, 0, 0.32);
--radius: 22px;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
min-height: 100vh;
color: var(--text);
background:
radial-gradient(circle at 12% 8%, rgba(49, 173, 255, 0.22), transparent 28rem),
radial-gradient(circle at 92% 18%, rgba(62, 242, 179, 0.14), transparent 26rem),
linear-gradient(145deg, #06101d 0%, #0a1727 52%, #07111f 100%);
line-height: 1.6;
}
a { color: inherit; }
.shell {
width: min(1160px, calc(100% - 36px));
margin: 0 auto;
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 24px 0;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
font-weight: 750;
letter-spacing: 0.01em;
text-decoration: none;
}
.brand-mark {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border: 1px solid var(--border);
border-radius: 14px;
background: linear-gradient(135deg, rgba(101, 212, 255, 0.22), rgba(121, 242, 192, 0.15));
box-shadow: inset 0 1px rgba(255, 255, 255, 0.08);
color: var(--accent);
font-size: 0.82rem;
}
.nav-links {
display: flex;
gap: 22px;
color: var(--muted);
font-size: 0.94rem;
}
.nav-links a {
text-decoration: none;
}
.nav-links a:hover,
.nav-links a:focus-visible {
color: var(--text);
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1.4fr) minmax(280px, 0.6fr);
gap: 42px;
align-items: center;
padding: 88px 0 80px;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 9px;
margin-bottom: 18px;
color: var(--accent-strong);
font-size: 0.82rem;
font-weight: 750;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.eyebrow::before {
content: "";
width: 28px;
height: 1px;
background: currentColor;
}
h1, h2, h3, p { margin-top: 0; }
h1 {
max-width: 830px;
margin-bottom: 22px;
font-size: clamp(3.1rem, 8vw, 6.5rem);
line-height: 0.96;
letter-spacing: -0.065em;
}
h1 span {
display: block;
color: transparent;
background: linear-gradient(90deg, var(--accent), var(--accent-strong));
background-clip: text;
-webkit-background-clip: text;
}
.hero-copy {
max-width: 720px;
margin-bottom: 30px;
color: var(--muted);
font-size: clamp(1.05rem, 2vw, 1.28rem);
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 13px;
}
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 48px;
padding: 0 20px;
border: 1px solid var(--border);
border-radius: 14px;
background: rgba(255, 255, 255, 0.04);
text-decoration: none;
font-weight: 700;
transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
}
.button.primary {
border-color: transparent;
background: linear-gradient(110deg, var(--accent), var(--accent-strong));
color: #032033;
}
.button:hover,
.button:focus-visible {
transform: translateY(-2px);
border-color: rgba(101, 212, 255, 0.58);
}
.status-card {
position: relative;
overflow: hidden;
padding: 28px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: linear-gradient(155deg, rgba(16, 36, 58, 0.94), rgba(8, 22, 38, 0.86));
box-shadow: var(--shadow);
}
.status-card::after {
content: "";
position: absolute;
right: -55px;
bottom: -75px;
width: 180px;
height: 180px;
border-radius: 50%;
background: rgba(101, 212, 255, 0.09);
filter: blur(6px);
}
.status-line {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 24px;
color: var(--accent-strong);
font-weight: 750;
}
.pulse {
width: 10px;
height: 10px;
border-radius: 50%;
background: currentColor;
box-shadow: 0 0 0 7px rgba(121, 242, 192, 0.12);
}
.status-list {
display: grid;
gap: 14px;
margin: 0;
padding: 0;
list-style: none;
color: var(--muted);
font-size: 0.94rem;
}
.status-list li {
display: flex;
justify-content: space-between;
gap: 18px;
padding-bottom: 13px;
border-bottom: 1px solid var(--border);
}
.status-list li:last-child {
padding-bottom: 0;
border-bottom: 0;
}
.status-list strong {
color: var(--text);
font-weight: 700;
text-align: right;
}
section { padding: 72px 0; }
.section-heading {
display: grid;
grid-template-columns: 0.75fr 1.25fr;
gap: 44px;
align-items: end;
margin-bottom: 34px;
}
h2 {
margin-bottom: 0;
font-size: clamp(2rem, 4vw, 3.25rem);
line-height: 1.05;
letter-spacing: -0.045em;
}
.section-heading p {
margin-bottom: 4px;
color: var(--muted);
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.card {
min-height: 100%;
padding: 26px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
box-shadow: inset 0 1px rgba(255, 255, 255, 0.035);
}
.card-number {
margin-bottom: 34px;
color: var(--accent);
font-size: 0.76rem;
font-weight: 800;
letter-spacing: 0.16em;
}
.card h3 {
margin-bottom: 10px;
font-size: 1.22rem;
}
.card p {
margin-bottom: 18px;
color: var(--muted);
font-size: 0.96rem;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag {
padding: 6px 9px;
border: 1px solid var(--border);
border-radius: 999px;
color: #cae1f3;
background: rgba(255, 255, 255, 0.025);
font-size: 0.75rem;
font-weight: 650;
}
.architecture {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.architecture-panel {
padding: 30px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: linear-gradient(145deg, rgba(17, 39, 62, 0.82), rgba(8, 22, 37, 0.9));
}
.architecture-panel h3 { margin-bottom: 18px; }
.flow {
display: grid;
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.flow li {
position: relative;
padding: 14px 16px 14px 44px;
border: 1px solid var(--border);
border-radius: 13px;
color: var(--muted);
background: rgba(4, 15, 27, 0.42);
}
.flow li::before {
content: "";
position: absolute;
left: 18px;
top: 50%;
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--accent);
transform: translateY(-50%);
box-shadow: 0 0 0 5px rgba(101, 212, 255, 0.09);
}
.security-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin: 0;
padding: 0;
list-style: none;
}
.security-list li {
padding: 13px 14px;
border: 1px solid var(--border);
border-radius: 12px;
color: var(--muted);
background: rgba(4, 15, 27, 0.42);
font-size: 0.91rem;
}
.callout {
display: flex;
align-items: center;
justify-content: space-between;
gap: 28px;
padding: 36px;
border: 1px solid rgba(101, 212, 255, 0.3);
border-radius: var(--radius);
background: linear-gradient(115deg, rgba(101, 212, 255, 0.12), rgba(121, 242, 192, 0.08));
}
.callout h2 { margin-bottom: 8px; }
.callout p { margin-bottom: 0; color: var(--muted); }
footer {
display: flex;
justify-content: space-between;
gap: 22px;
padding: 34px 0 50px;
border-top: 1px solid var(--border);
color: var(--muted);
font-size: 0.9rem;
}
@media (max-width: 900px) {
.hero,
.section-heading,
.architecture {
grid-template-columns: 1fr;
}
.hero { padding-top: 54px; }
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.section-heading { gap: 14px; }
}
@media (max-width: 620px) {
.shell { width: min(100% - 24px, 1160px); }
.nav-links { display: none; }
.hero { padding: 42px 0 54px; }
h1 { font-size: clamp(3rem, 17vw, 4.6rem); }
section { padding: 54px 0; }
.grid,
.security-list { grid-template-columns: 1fr; }
.callout,
footer { align-items: flex-start; flex-direction: column; }
}
</style>
</head>
<body>
<header class="shell">
<nav class="nav" aria-label="Primary navigation">
<a class="brand" href="#top">
<span class="brand-mark" aria-hidden="true">JP</span>
<span>Jay Phillips</span>
</a>
<div class="nav-links">
<a href="#skills">Skills</a>
<a href="#projects">Projects</a>
<a href="#platform">Platform</a>
</div>
</nav>
</header>
<main id="top">
<div class="shell hero">
<div>
<div class="eyebrow">Systems Administrator · Infrastructure Engineer</div>
<h1>Building resilient <span>infrastructure.</span></h1>
<p class="hero-copy">
I design, automate, secure, and operate enterprise and home-lab platforms across
Windows Server, VMware, Linux, Kubernetes, GitOps, and Microsoft systems management.
</p>
<div class="actions">
<a class="button primary" href="#projects">Explore projects</a>
<a class="button" href="#platform">View platform design</a>
</div>
</div>
<aside class="status-card" aria-label="Portfolio deployment status">
<div class="status-line"><span class="pulse" aria-hidden="true"></span>Portfolio foundation online</div>
<ul class="status-list">
<li><span>Orchestration</span><strong>Kubernetes 1.36</strong></li>
<li><span>Delivery</span><strong>Argo CD GitOps</strong></li>
<li><span>Network policy</span><strong>Calico</strong></li>
<li><span>Ingress</span><strong>NGINX + TLS</strong></li>
<li><span>Workload identity</span><strong>Token disabled</strong></li>
</ul>
</aside>
</div>
<section id="skills" class="shell">
<div class="section-heading">
<h2>Core capabilities</h2>
<p>
Hands-on administration supported by automation, security controls, observability,
documentation, and repeatable deployment practices.
</p>
</div>
<div class="grid">
<article class="card">
<div class="card-number">01 / CLOUD NATIVE</div>
<h3>Kubernetes &amp; GitOps</h3>
<p>Multi-node clusters, Argo CD, ingress, certificates, persistent storage, RBAC, policies, and application lifecycle management.</p>
<div class="tags"><span class="tag">Kubernetes</span><span class="tag">Argo CD</span><span class="tag">Kustomize</span><span class="tag">Longhorn</span></div>
</article>
<article class="card">
<div class="card-number">02 / MICROSOFT</div>
<h3>Windows Infrastructure</h3>
<p>Windows Server, Active Directory, Group Policy, Configuration Manager, Operations Manager, SharePoint, and PowerShell.</p>
<div class="tags"><span class="tag">Windows Server</span><span class="tag">SCCM</span><span class="tag">SCOM</span><span class="tag">SharePoint</span></div>
</article>
<article class="card">
<div class="card-number">03 / AUTOMATION</div>
<h3>Automation &amp; IaC</h3>
<p>Repeatable infrastructure operations using PowerShell, Bash, Git, Terraform, Ansible, APIs, and declarative configuration.</p>
<div class="tags"><span class="tag">PowerShell</span><span class="tag">Terraform</span><span class="tag">Ansible</span><span class="tag">Git</span></div>
</article>
<article class="card">
<div class="card-number">04 / PLATFORMS</div>
<h3>Virtualization &amp; Linux</h3>
<p>VMware vSphere administration, Linux server operations, networking, storage, patching, troubleshooting, and capacity planning.</p>
<div class="tags"><span class="tag">VMware</span><span class="tag">Ubuntu</span><span class="tag">Rocky Linux</span><span class="tag">NFS</span></div>
</article>
<article class="card">
<div class="card-number">05 / OBSERVABILITY</div>
<h3>Monitoring &amp; Reliability</h3>
<p>Metrics, logging, alerting, dashboards, service health, failure analysis, and operational visibility across infrastructure layers.</p>
<div class="tags"><span class="tag">Prometheus</span><span class="tag">Grafana</span><span class="tag">Loki</span><span class="tag">Alertmanager</span></div>
</article>
<article class="card">
<div class="card-number">06 / SECURITY</div>
<h3>Secure Operations</h3>
<p>Least privilege, service identities, network segmentation, certificate management, secret handling, backups, and recovery planning.</p>
<div class="tags"><span class="tag">RBAC</span><span class="tag">TLS</span><span class="tag">NetworkPolicy</span><span class="tag">GitOps</span></div>
</article>
</div>
</section>
<section id="projects" class="shell">
<div class="section-heading">
<h2>Featured work</h2>
<p>
The full case studies will document the problem, architecture, implementation,
troubleshooting decisions, security controls, and operational results.
</p>
</div>
<div class="grid">
<article class="card">
<div class="card-number">CASE STUDY / 01</div>
<h3>Production-style Kubernetes home lab</h3>
<p>A four-node Kubernetes platform with load balancing, persistent storage, certificate automation, ingress, backups, and segmented applications.</p>
<div class="tags"><span class="tag">4 nodes</span><span class="tag">Calico</span><span class="tag">MetalLB</span><span class="tag">Longhorn</span></div>
</article>
<article class="card">
<div class="card-number">CASE STUDY / 02</div>
<h3>GitOps application delivery</h3>
<p>Declarative application deployment and reconciliation through Argo CD, Git-based change control, Kustomize, and automated image workflows.</p>
<div class="tags"><span class="tag">Argo CD</span><span class="tag">Git</span><span class="tag">Kustomize</span><span class="tag">Self-healing</span></div>
</article>
<article class="card">
<div class="card-number">CASE STUDY / 03</div>
<h3>Enterprise monitoring stack</h3>
<p>Centralized metrics, logs, dashboards, uptime monitoring, and email alerting for cluster and application health.</p>
<div class="tags"><span class="tag">Prometheus</span><span class="tag">Grafana</span><span class="tag">Loki</span><span class="tag">Uptime Kuma</span></div>
</article>
</div>
</section>
<section id="platform" class="shell">
<div class="section-heading">
<h2>This site is part of the demonstration.</h2>
<p>
The portfolio is deployed as an isolated, policy-controlled workload rather than a
conventional static hosting account. The architecture itself is a portfolio artifact.
</p>
</div>
<div class="architecture">
<article class="architecture-panel">
<h3>Delivery path</h3>
<ol class="flow">
<li>Git repository stores the desired state</li>
<li>Argo CD reconciles a restricted AppProject</li>
<li>Kustomize renders application resources</li>
<li>ingress-nginx routes HTTPS traffic</li>
<li>cert-manager issues the internal TLS certificate</li>
</ol>
</article>
<article class="architecture-panel">
<h3>Security controls</h3>
<ul class="security-list">
<li>Restricted Pod Security</li>
<li>Non-root container</li>
<li>Read-only root filesystem</li>
<li>All capabilities dropped</li>
<li>Default-deny networking</li>
<li>No outbound network access</li>
<li>No API token mounted</li>
<li>Resource quotas and limits</li>
</ul>
</article>
</div>
</section>
<section class="shell">
<div class="callout">
<div>
<h2>Foundation complete. Case studies next.</h2>
<p>This internal staging release will grow into a public portfolio, short résumé, architecture library, and safe interactive demonstration environment.</p>
</div>
<a class="button primary" href="#top">Back to top</a>
</div>
</section>
</main>
<footer class="shell">
<span>© 2026 Jay Phillips</span>
<span>Deployed with Kubernetes, Argo CD, Kustomize, Calico, ingress-nginx, and cert-manager.</span>
</footer>
</body>
</html>