---
updatedAt: 2026-10-07T18:54:37.000Z
agentTools:
  projectIndex: https://docs.sixfab.com/llms.txt
---

# Build Your Own Secure VPN

Deploy a WireGuard VPN server as a container on ALPON X5 AI or ALPON X4 through ALPON Cloud: generate client keys, build and push the server image, deploy it with Host Network and Privileged enabled, configure the client, and test the tunnel with a sample Nginx container.

<HTMLBlock>{`
<style>
/* Theme guard: the docs container (.rm-ReadMe) decides light/dark once it carries
   data-color-mode; the html element is only the fallback before hydration. The
   :where(...) parts on the dark selectors below stop a light docs pane inside a
   non-light html element (ReadMe admin view) from picking up dark colors.
   They add no specificity. */
/* ══════════════════════════════════════════════════════════════════════════
   SIXFAB DOCS · ALPON TUTORIAL STYLESHEET (canonical, shared by every page
   under ALPON › Tutorials). Do not edit per page: change it here and re-stamp
   into all tutorials so the block stays byte-identical.
   Components: hero · meta · pill · aeo · toc · step-list · terminal · callout
   · spec-card · figure/lightbox · issue · stack · path · table · related guides.
   Page-specific components go AFTER the "END CANONICAL" marker.
   ══════════════════════════════════════════════════════════════════════════ */
/* ════════════════════════════════════════════════════════════════════════
   FONTS - Gilroy (5 weights) + JetBrains Mono (2 weights)
   Per design language §1.1 starter boilerplate.
═════════════════════════════════════════════════════════════════════════ */
@font-face { font-family:'Gilroy'; src:url('https://sixfab.com/wp-content/fonts/Gilroy-Regular.woff2')   format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'Gilroy'; src:url('https://sixfab.com/wp-content/fonts/Gilroy-Medium.woff2')    format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'Gilroy'; src:url('https://sixfab.com/wp-content/fonts/Gilroy-SemiBold.woff2')  format('woff2'); font-weight:600; font-display:swap; }
@font-face { font-family:'Gilroy'; src:url('https://sixfab.com/wp-content/fonts/Gilroy-Bold.woff2')      format('woff2'); font-weight:700; font-display:swap; }
@font-face { font-family:'Gilroy'; src:url('https://sixfab.com/wp-content/fonts/Gilroy-ExtraBold.woff2') format('woff2'); font-weight:800; font-display:swap; }
@font-face { font-family:'JetBrains Mono'; src:url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@2.304/fonts/webfonts/JetBrainsMono-Regular.woff2') format('woff2'); font-weight:400; font-style:normal; font-display:swap; }
@font-face { font-family:'JetBrains Mono'; src:url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono@2.304/fonts/webfonts/JetBrainsMono-Medium.woff2') format('woff2'); font-weight:500; font-style:normal; font-display:swap; }

/* ════════════════════════════════════════════════════════════════════════
   DESIGN TOKENS - six-tier system, scoped to .sf-doc
═════════════════════════════════════════════════════════════════════════ */
.sf-doc {
  --primary:       #034860;
  --secondary:     #00E0B8;
  --tertiary:      #1B2D45;
  --neutral:       #E8F5F0;
  --white:         #FFFFFF;
  --light:         #F6F8F9;
  --line:          #DDE6EC;
  --slate-light:   #94A8B5;
  --slate:         #52667A;
  --deep-ink:      #10202E;
  --ai-iris:       #6B47F0;
  --ai-mist:       #F0EDFF;
  --alpon-purple:  #4F008E;
  --alpon-cyan:    #00A6C8;   /* ALPON Gradient end */
  --alpon-cyan-deep: #007A8F; /* cyan surface for white text (WCAG AA 5.0:1) */
  --success:       #00B894;
  --warning:       #F59E0B;
  --danger:        #DC2626;
  --info:          #429CBF;
  --success-text:  #047857;
  --warning-text:  #92400E;
  --danger-text:   #B91C1C;
  --info-text:     #0E4A66;
  --success-bg:    #E0F5EE;
  --warning-bg:    #FDF1DC;
  --danger-bg:     #FBE5E5;
  --info-bg:       #DCEFF7;
  --on-primary:    #FFFFFF;

  --xs:   4px;  --sm:   8px;  --md:  16px;  --lg:  24px;
  --xl:  32px;  --2xl: 48px;  --3xl: 64px;

  --r-sm: 4px; --r-md: 6px; --r-lg: 8px; --r-xl: 12px; --r-2xl: 16px; --r-full: 9999px;

  --shadow-sm: 0 1px 2px rgba(10,23,33,.04);
  --shadow-md: 0 4px 12px rgba(10,23,33,.06);
  --shadow-lg: 0 8px 24px rgba(10,23,33,.08);

  --font-sans: 'Gilroy', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace;

  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--deep-ink);
  -webkit-font-smoothing: antialiased;
}
.sf-doc * { box-sizing: border-box; }

/* ════════════════════════════════════════════════════════════════════════
   BASE TYPOGRAPHY
═════════════════════════════════════════════════════════════════════════ */
.sf-doc h1 {
  font-size: 3rem; font-weight: 800; letter-spacing: -0.03em;
  line-height: 1.08; color: var(--deep-ink); margin: 0 0 16px;
}
.sf-doc h2 {
  font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em;
  line-height: 1.2; color: var(--primary);
  margin: 56px 0 16px; padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}
.sf-doc h2:first-of-type { margin-top: 0; }
.sf-doc h3 {
  font-size: 1.125rem; font-weight: 700; letter-spacing: -0.015em;
  line-height: 1.3; color: var(--deep-ink); margin: 28px 0 10px;
}
.sf-doc h4 {
  font-size: 0.875rem; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--slate);
  margin: 18px 0 6px;
}
.sf-doc p {
  font-size: 1rem; font-weight: 400; line-height: 1.6;
  color: var(--deep-ink); margin: 0 0 14px;
}
.sf-doc ul, .sf-doc ol { padding-left: 24px; margin: 8px 0 16px; }
.sf-doc li { font-size: 1rem; line-height: 1.6; color: var(--deep-ink); margin-bottom: 6px; }
.sf-doc ul li::marker { color: var(--primary); }
.sf-doc a { color: var(--primary); text-decoration: none; }
.sf-doc a:hover { color: var(--deep-ink); text-decoration: underline; }
.sf-doc code {
  font-family: var(--font-mono); font-size: 0.875rem;
  background: var(--light); color: var(--primary);
  padding: 1px 5px; border-radius: var(--r-sm); border: 0;
}
.sf-doc strong { color: var(--deep-ink); font-weight: 700; }
.sf-doc hr { border: 0; height: 1px; background: var(--line); margin: 48px 0; }

/* ════════════════════════════════════════════════════════════════════════
   META BAR
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  font-family: var(--font-mono); font-size: 0.75rem;
  color: var(--slate); margin: 0 0 24px;
}
.sf-doc .sf-meta__badge {
  font-family: var(--font-sans); font-weight: 700; font-size: 0.75rem;
  padding: 3px 8px; border-radius: var(--r-sm);
  background: var(--light); color: var(--primary);
  border: 1px solid var(--line); letter-spacing: 0;
  text-decoration: none !important;
}
.sf-doc .sf-meta__badge:hover { background: var(--neutral); color: var(--primary); }
.sf-doc .sf-meta__badge--mint {
  background: var(--neutral); color: var(--primary); border-color: var(--neutral);
}
.sf-doc .sf-meta__sep { color: var(--line); }

/* ════════════════════════════════════════════════════════════════════════
   HERO - compact, default Tier 1 surface
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-hero {
  padding: var(--xl); margin: 0 0 24px;
  background: var(--light); border: 1px solid var(--line);
  border-radius: var(--r-2xl);
}
.sf-doc .sf-hero__copy { min-width: 0; }
.sf-doc .sf-hero p.sf-hero__title,
.sf-doc .sf-hero .sf-hero__title {
  font-family: var(--font-sans);
  font-size: 2.5rem; font-weight: 800; line-height: 1.08;
  letter-spacing: -0.03em; color: var(--deep-ink); margin: 0 0 14px;
}
.sf-doc .sf-hero p.sf-hero__lead,
.sf-doc .sf-hero .sf-hero__lead {
  font-size: 1.0625rem; line-height: 1.6;
  color: var(--deep-ink); margin: 0 0 18px;
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
}
.sf-doc .sf-hero__chips { display: flex; flex-wrap: wrap; gap: 6px; }

/* ════════════════════════════════════════════════════════════════════════
   PILLS
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-sans); font-size: 0.75rem; font-weight: 600;
  padding: 4px 10px; border-radius: var(--r-sm);
  background: var(--light); color: var(--deep-ink);
  border: 1px solid var(--line); white-space: nowrap;
  vertical-align: middle; line-height: 1.4;
}
.sf-doc .sf-pill--alpon-x5 {
  background: var(--alpon-purple); color: #FFFFFF !important; border-color: var(--alpon-purple);
}
.sf-doc .sf-pill--alpon-x4 {
  background: var(--alpon-cyan-deep); color: #FFFFFF !important; border-color: var(--alpon-cyan-deep);
}
.sf-doc .sf-pill--success { background: var(--success-bg); color: var(--success-text); border-color: var(--success-bg); }
.sf-doc .sf-pill--info    { background: var(--info-bg);    color: var(--info-text);    border-color: var(--info-bg); }
.sf-doc .sf-pill--warning { background: var(--warning-bg); color: var(--warning-text); border-color: var(--warning-bg); }

/* ════════════════════════════════════════════════════════════════════════
   AEO ANSWER BLOCK - default Tier 1
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-aeo {
  position: relative;
  background: var(--light);
  border: 1px solid var(--line);
  border-left: 4px solid var(--primary);
  border-radius: var(--r-md);
  padding: var(--lg) var(--xl);
  margin: 0 0 var(--xl);
}
.sf-doc .sf-aeo__label {
  display: block; font-family: var(--font-sans);
  font-size: 1.375rem; font-weight: 700;
  line-height: 1.25; letter-spacing: -0.015em;
  text-transform: none; color: var(--primary);
  margin: 0 0 var(--sm);
}
.sf-doc .sf-aeo__text {
  font-size: 1rem;
  line-height: 1.6;
  color: var(--deep-ink);
  margin: 0;
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
}
.sf-doc .sf-aeo__text strong { color: var(--primary); font-weight: 600; }
.sf-doc .sf-aeo__text code { font-size: 0.8125rem; }

/* ════════════════════════════════════════════════════════════════════════
   IN THIS GUIDE - sf-toc
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-toc {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  padding: var(--md) var(--lg);
  background: var(--light); border: 1px solid var(--line);
  border-radius: var(--r-lg); margin: 0 0 var(--xl);
}
.sf-doc .sf-toc__label {
  font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 500;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--slate); margin-right: var(--sm);
}
.sf-doc .sf-toc__item {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; background: var(--white);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: 0.8125rem; font-weight: 600;
  color: var(--deep-ink) !important; text-decoration: none !important;
  transition: border-color .15s, box-shadow .15s; white-space: nowrap;
}
.sf-doc .sf-toc__item:hover,
.sf-doc .sf-toc__item:focus {
  background: var(--primary); color: var(--white) !important;
  border-color: var(--primary); text-decoration: none !important;
}
.sf-doc .sf-toc__item:hover .sf-toc__num,
.sf-doc .sf-toc__item:focus .sf-toc__num { color: var(--secondary); }
.sf-doc .sf-toc__num {
  font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 500;
  color: var(--slate);
}

/* ════════════════════════════════════════════════════════════════════════
   ICONS
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-icon { width: 16px; height: 16px; flex-shrink: 0; vertical-align: -2px; }
.sf-doc .sf-icon--stroke { fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }

/* ════════════════════════════════════════════════════════════════════════
   SPEC CARDS - identical to alpon-x4-specifications.html tables
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-spec-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 16px; margin: 0 0 24px;
}
.sf-doc .sf-spec-card {
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-xl); padding: 20px 22px;
  display: flex; flex-direction: column;
  margin: 16px 0 24px; transition: border-color .15s;
}
.sf-doc .sf-spec-grid .sf-spec-card { margin: 0; }
.sf-doc .sf-spec-card:hover { border-color: var(--slate-light); }
.sf-doc .sf-spec-card__head {
  display: flex; align-items: center; gap: 10px;
  padding-bottom: 14px; border-bottom: 1px solid var(--line);
  margin-bottom: 14px;
}
.sf-doc .sf-spec-card__icon {
  width: 32px; height: 32px; border-radius: var(--r-md);
  background: var(--light); color: var(--primary);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.sf-doc .sf-spec-card__icon svg { width: 18px; height: 18px; }
.sf-doc .sf-spec-card__title {
  font-size: 1rem; font-weight: 700;
  color: var(--primary); letter-spacing: -0.01em;
}
.sf-doc .sf-spec-card__body { display: flex; flex-direction: column; }
.sf-doc .sf-spec-row {
  display: grid; grid-template-columns: minmax(160px, 30%) 1fr;
  gap: 24px; align-items: baseline;
  padding: 11px 0; border-bottom: 1px dashed var(--line);
}
.sf-doc .sf-spec-row:first-child { padding-top: 4px; }
.sf-doc .sf-spec-row:last-child { border-bottom: 0; padding-bottom: 4px; }
.sf-doc .sf-spec-row__key {
  font-size: 0.9375rem; font-weight: 700;
  color: var(--deep-ink); line-height: 1.5;
}
.sf-doc .sf-spec-row__val {
  font-size: 0.9375rem; font-weight: 500;
  color: var(--deep-ink); line-height: 1.55;
  text-align: left; min-width: 0;
}
.sf-doc .sf-spec-row__val code { font-size: 0.875rem; }
.sf-doc .sf-spec-row__val em { font-style: italic; color: var(--slate); font-weight: 500; }

/* ════════════════════════════════════════════════════════════════════════
   CALLOUT
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-callout {
  display: flex; flex-direction: column; gap: 6px;
  border-radius: var(--r-md); padding: 14px 18px;
  margin: 20px 0; border-left: 4px solid;
}
.sf-doc .sf-callout__header {
  display: flex; align-items: center; gap: 8px;
  font-size: 0.875rem; font-weight: 700; line-height: 1;
}
.sf-doc .sf-callout__header svg { width: 16px; height: 16px; flex-shrink: 0; }
.sf-doc .sf-callout__content { font-size: 0.9375rem; line-height: 1.6; color: var(--deep-ink); }
.sf-doc .sf-callout__content p { margin: 0 0 6px; font-size: 0.9375rem; color: inherit; }
.sf-doc .sf-callout__content p:last-child { margin-bottom: 0; }
.sf-doc .sf-callout__content code { background: rgba(0,0,0,0.06); border: 0; }
.sf-doc .sf-callout--info     { background: var(--info-bg);    border-left-color: var(--info); }
.sf-doc .sf-callout--info     .sf-callout__header { color: var(--info-text); }
.sf-doc .sf-callout--success  { background: var(--success-bg); border-left-color: var(--success); }
.sf-doc .sf-callout--success  .sf-callout__header { color: var(--success-text); }
.sf-doc .sf-callout--warning  { background: var(--warning-bg); border-left-color: var(--warning); }
.sf-doc .sf-callout--warning  .sf-callout__header { color: var(--warning-text); }
.sf-doc .sf-callout--danger   { background: var(--danger-bg);  border-left-color: var(--danger); }
.sf-doc .sf-callout--danger   .sf-callout__header { color: var(--danger-text); }

/* ════════════════════════════════════════════════════════════════════════
   STEP LIST
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-step-list {
  position: relative; padding: 0;
  margin: var(--lg) 0 var(--xl);
  list-style: none !important;
}
.sf-doc .sf-step-list::before {
  content: ''; position: absolute;
  left: 23px; top: 48px; bottom: 24px;
  width: 2px; background: var(--line); z-index: 0;
}
.sf-doc .sf-step {
  position: relative; z-index: 1;
  padding-bottom: var(--2xl); scroll-margin-top: 24px;
}
.sf-doc .sf-step:last-child { padding-bottom: 0; }
.sf-doc .sf-step__header {
  display: grid; grid-template-columns: 48px 1fr;
  align-items: center; gap: 0 22px;
  margin-bottom: var(--md);
}
.sf-doc .sf-step__num {
  width: 48px; height: 48px;
  border-radius: var(--r-full);
  background: var(--primary); color: var(--on-primary);
  font-family: var(--font-sans); font-size: 1.0625rem; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 5px var(--white), 0 4px 10px rgba(10,23,33,.10);
  align-self: center; flex-shrink: 0;
}
.sf-doc .sf-step__title {
  font-family: var(--font-sans); font-size: 1.5rem; font-weight: 700;
  letter-spacing: -0.02em; color: var(--deep-ink);
  line-height: 1.15; margin: 0; padding: 0;
  align-self: center; display: flex; align-items: center;
  gap: 10px; flex-wrap: wrap;
}
.sf-doc .sf-step__title code { font-size: 1.125rem; }
.sf-doc .sf-step__body { padding-left: 70px; min-width: 0; }
.sf-doc .sf-step__body p { font-size: 1rem; line-height: 1.65; margin: 0 0 14px; }
.sf-doc .sf-step__body ul,
.sf-doc .sf-step__body ol { margin: 6px 0 14px; }
.sf-doc .sf-step__body ul li,
.sf-doc .sf-step__body ol li { font-size: 0.9375rem; margin-bottom: 4px; }

/* ════════════════════════════════════════════════════════════════════════
   TERMINAL
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-terminal {
  background: #0d1117 !important;
  border: 1px solid #30363d;
  border-radius: var(--r-lg);
  margin: 14px 0 18px;
  overflow: hidden; box-shadow: var(--shadow-md);
}
.sf-doc .sf-terminal__header {
  display: flex !important; align-items: center; gap: 6px;
  padding: 10px 14px; background: #161b22 !important;
  border-bottom: 1px solid #30363d;
}
.sf-doc .sf-terminal__dot {
  width: 11px; height: 11px;
  border-radius: var(--r-full); flex-shrink: 0;
}
.sf-doc .sf-terminal__title {
  margin-left: 8px; font-family: var(--font-mono) !important;
  font-size: 0.75rem !important; color: #8B949E !important; font-weight: 500;
}
.sf-doc .sf-terminal__body { padding: 14px 16px; overflow-x: auto; }
.sf-doc .sf-terminal__body pre {
  margin: 0 !important; padding: 0 !important;
  background: transparent !important; border: 0 !important;
  font-family: var(--font-mono) !important;
  font-size: 0.825rem !important; line-height: 1.65 !important;
  color: #e6edf3 !important; white-space: pre;
}
.sf-doc .t-dim   { color: #8B949E !important; }
.sf-doc .t-green { color: #56d364 !important; font-weight: 600; }
.sf-doc .t-blue  { color: #79c0ff !important; }
.sf-doc .t-yel   { color: #e3b341 !important; }
.sf-doc .t-pp    { color: #d2a8ff !important; }
.sf-doc .t-red   { color: #ff7b72 !important; }
.sf-doc .t-hl    { color: #56d364 !important; font-weight: 600; }

/* ════════════════════════════════════════════════════════════════════════
   COMPARISON GRID - Stripe-style two-column path picker
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-paths {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
  margin: 16px 0 32px;
}
.sf-doc .sf-path {
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-xl); padding: 22px 24px;
  display: flex; flex-direction: column;
  transition: border-color .15s, box-shadow .15s;
}
.sf-doc .sf-path:hover { border-color: var(--slate-light); box-shadow: var(--shadow-md); }
.sf-doc .sf-path__tag {
  font-family: var(--font-mono); font-size: 0.6875rem;
  font-weight: 500; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--slate);
  margin-bottom: 8px;
}
.sf-doc .sf-path__title {
  font-size: 1.125rem; font-weight: 700;
  color: var(--deep-ink); margin: 0 0 8px;
  letter-spacing: -0.01em;
}
.sf-doc .sf-path__desc {
  font-size: 0.9375rem; line-height: 1.55;
  color: var(--slate); margin: 0 0 14px;
}
.sf-doc .sf-path__list {
  list-style: none !important;
  padding: 0 !important;
  margin: 0 0 14px !important;
}
.sf-doc .sf-path__list li {
  font-size: 0.9375rem;
  color: var(--deep-ink);
  padding: 6px 0 6px 28px;
  position: relative;
  margin: 0;
  line-height: 1.5;
  list-style: none !important;
}
.sf-doc .sf-path__list li::marker { content: '' !important; }
.sf-doc .sf-path__list li::before {
  content: ''; position: absolute;
  left: 0; top: 10px;
  width: 16px; height: 16px;
  background: var(--neutral);
  border-radius: var(--r-full);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23034860' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 11px;
  flex-shrink: 0;
}
.sf-doc .sf-path__foot {
  margin-top: auto; padding-top: 14px;
  border-top: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 0.75rem;
  color: var(--slate);
}
.sf-doc .sf-path__foot strong { color: var(--primary); font-weight: 600; }

/* ════════════════════════════════════════════════════════════════════════
   DOCKERFILE STACK PICKER
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-stack-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px; margin: 16px 0 24px;
}
.sf-doc .sf-stack {
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 18px 20px;
  text-decoration: none !important;
  transition: border-color .15s, transform .15s, box-shadow .15s;
  display: flex; align-items: center; gap: 14px;
  color: var(--deep-ink) !important;
}
.sf-doc .sf-stack__badge {
  width: 44px; height: 44px;
  border-radius: var(--r-md);
  background: var(--neutral);
  color: var(--primary);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-size: 1rem; font-weight: 700;
  letter-spacing: -0.02em; flex-shrink: 0;
}
.sf-doc .sf-stack__copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sf-doc .sf-stack:hover {
  border-color: var(--primary); transform: translateY(-2px);
  box-shadow: var(--shadow-md); text-decoration: none !important;
}
.sf-doc .sf-stack__lang {
  font-family: var(--font-sans); font-size: 0.9375rem;
  font-weight: 700; color: var(--deep-ink); letter-spacing: -0.01em;
}
.sf-doc .sf-stack__base {
  font-family: var(--font-mono); font-size: 0.75rem;
  color: var(--slate); font-weight: 500;
}

/* ════════════════════════════════════════════════════════════════════════
   FIGURE & LIGHTBOX
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-figure {
  margin: 16px 0; border-radius: var(--r-lg);
  overflow: hidden; border: 1px solid var(--line);
  background: var(--white);
}
.sf-doc .sf-figure a {
  display: block; cursor: zoom-in;
  text-decoration: none !important;
}
.sf-doc .sf-figure img {
  width: 100%; height: auto; display: block;
  transition: opacity .15s;
}
.sf-doc .sf-figure a:hover img { opacity: 0.94; }
.sf-doc .sf-figure figcaption {
  font-family: var(--font-sans);
  font-size: 0.8125rem; color: var(--slate);
  padding: 10px 14px; background: var(--light);
  border-top: 1px solid var(--line);
  line-height: 1.5; font-style: italic;
}
.sf-doc .sf-fig-anchor {
  display: block; position: relative;
  top: -80px; visibility: hidden;
}
.sf-doc .sf-lightbox {
  display: none; position: fixed; inset: 0;
  z-index: 9999; background: rgba(10,23,33,0.92);
  align-items: center; justify-content: center; padding: 24px;
}
.sf-doc .sf-lightbox:target { display: flex; }
.sf-doc .sf-lightbox__backdrop {
  position: absolute; inset: 0; cursor: zoom-out;
}
.sf-doc .sf-lightbox__close {
  position: absolute; top: 18px; right: 18px;
  width: 36px; height: 36px;
  background: rgba(255,255,255,0.12);
  color: #fff !important;
  border: 1px solid rgba(255,255,255,0.25);
  border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  z-index: 2; text-decoration: none !important;
  transition: background .15s, transform .15s;
}
.sf-doc .sf-lightbox__close:hover {
  background: rgba(255,255,255,0.22); transform: rotate(90deg);
}
.sf-doc .sf-lightbox__close svg {
  width: 18px; height: 18px;
  stroke: #fff; stroke-width: 2.2; fill: none;
}
.sf-doc .sf-lightbox__inner {
  position: relative; z-index: 1;
  max-width: 1200px; width: 100%;
  display: flex; flex-direction: column; align-items: center;
}
.sf-doc .sf-lightbox__img-wrap {
  display: block; cursor: zoom-out;
  text-decoration: none !important; max-width: 100%;
}
.sf-doc .sf-lightbox__img-wrap img {
  display: block; max-width: 100%;
  max-height: 78vh; height: auto;
  border-radius: var(--r-md);
  border: 1px solid rgba(255,255,255,0.12);
}
.sf-doc .sf-lightbox__caption {
  margin-top: 14px;
  color: rgba(255,255,255,0.85);
  font-size: 0.875rem;
  font-family: var(--font-sans);
  text-align: center; max-width: 700px; line-height: 1.5;
}

/* ════════════════════════════════════════════════════════════════════════
   ISSUE / TROUBLESHOOTING - mirrors alpon-create-wifi-hotspot pattern
   summary + tag pill + Symptom/Cause/Fix body
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-issue {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  margin: 0 0 12px;
  overflow: hidden;
  transition: border-color .15s, box-shadow .15s;
}
.sf-doc .sf-issue:hover { border-color: var(--slate-light); }
.sf-doc .sf-issue[open] {
  border-color: var(--primary);
  box-shadow: var(--shadow-sm);
}
.sf-doc .sf-issue__summary {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 20px;
  font-family: var(--font-sans);
  font-size: 1rem; font-weight: 700;
  color: var(--deep-ink);
  cursor: pointer; list-style: none;
}
.sf-doc .sf-issue__summary::-webkit-details-marker { display: none; }
.sf-doc .sf-issue__summary > span:nth-child(2) {
  flex: 1; line-height: 1.45; font-weight: 700;
}
.sf-doc .sf-issue__tag {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: var(--r-sm);
  background: var(--info-bg);
  color: var(--info-text);
  border: 1px solid var(--info-bg);
  flex-shrink: 0;
  white-space: nowrap;
}
.sf-doc .sf-issue__tag--hw {
  background: var(--danger-bg); color: var(--danger-text); border-color: var(--danger-bg);
}
.sf-doc .sf-issue__tag--sw {
  background: var(--ai-mist); color: var(--ai-iris); border-color: var(--ai-mist);
}
.sf-doc .sf-issue__tag--auth {
  background: var(--warning-bg); color: var(--warning-text); border-color: var(--warning-bg);
}
.sf-doc .sf-issue__tag--net {
  background: var(--info-bg); color: var(--info-text); border-color: var(--info-bg);
}
.sf-doc .sf-issue__tag--config {
  background: var(--neutral); color: var(--primary); border-color: var(--neutral);
}
.sf-doc .sf-issue__chev {
  width: 18px; height: 18px;
  color: var(--slate);
  flex-shrink: 0;
  transition: transform .2s;
}
.sf-doc .sf-issue[open] .sf-issue__chev {
  transform: rotate(180deg);
  color: var(--primary);
}
.sf-doc .sf-issue__body {
  padding: 4px 20px 20px;
  border-top: 1px solid var(--line);
}
.sf-doc .sf-issue__body h4 {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--slate);
  margin: 18px 0 8px;
}
.sf-doc .sf-issue__body h4:first-child { margin-top: 14px; }
.sf-doc .sf-issue__body p {
  font-size: 0.9375rem;
  line-height: 1.65;
  margin: 0 0 8px;
  color: var(--deep-ink);
}
.sf-doc .sf-issue__body p:last-child { margin-bottom: 0; }
.sf-doc .sf-issue__body code { font-size: 0.8125rem; }
.sf-doc .sf-issue__body .sf-terminal { margin: 10px 0 0; }

/* ════════════════════════════════════════════════════════════════════════
   DARK MODE
═════════════════════════════════════════════════════════════════════════ */
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc {
  --primary: #034860; --secondary: #00E0B8; --tertiary: #1B2D45;
  --neutral: #E8F5F0; --white: #FFFFFF; --light: #14222F;
  --line: #2A3A4D; --slate-light: #94A8B5; --slate: #B5C3CE;
  --deep-ink: #E8F5F0;
  --success-bg: #052E1B; --warning-bg: #2A1F05;
  --danger-bg: #2A0E0E; --info-bg: #0A2E3F;
  --success-text: #34D399; --warning-text: #FBBF24;
  --danger-text: #F87171; --info-text: #5CD3EE;
  --ai-iris: #B59CFF; --ai-mist: #1F1A33;
  color: var(--deep-ink);
}
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc h1,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc h3,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc h4,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc strong { color: #FFFFFF; }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc h2 { color: var(--secondary); border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc p,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc li,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-aeo__text,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-callout__content { color: var(--neutral); }
html[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc a { color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc code { background: rgba(255,255,255,0.06); color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc hr { background: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc ul li::marker { color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-meta__badge { background: rgba(255,255,255,0.06); color: var(--secondary); border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-aeo { background: rgba(255,255,255,0.04); border-color: var(--line); border-left-color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-aeo__label { color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-aeo__text strong { color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-hero { background: rgba(255,255,255,0.04); border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-hero p.sf-hero__title,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-hero .sf-hero__title { color: #FFFFFF; }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-hero p.sf-hero__lead,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-hero .sf-hero__lead { color: var(--neutral); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-toc { background: rgba(255,255,255,0.04); border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-toc__item { background: transparent; color: var(--neutral) !important; border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-toc__item:hover { background: var(--secondary); color: var(--tertiary) !important; border-color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-toc__item:hover .sf-toc__num { color: var(--tertiary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-card { background: rgba(255,255,255,0.04); border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-card:hover { border-color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-card__head { border-bottom-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-card__icon { background: rgba(0,224,184,0.10); color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-card__title { color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-row { border-bottom-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-row__key { color: #FFFFFF; }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-row__val { color: var(--neutral); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-spec-row__val code { background: rgba(255,255,255,0.06); color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-pill { background: rgba(255,255,255,0.06); color: var(--neutral); border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-path { background: rgba(255,255,255,0.04); border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-path__list li { color: var(--neutral); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-path__list li::before { background: rgba(0,224,184,0.14); background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2300E0B8' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>"); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-path__foot { border-top-color: var(--line); color: var(--slate); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-path__foot strong { color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-stack { background: rgba(255,255,255,0.04); border-color: var(--line); color: var(--neutral) !important; }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-stack:hover { border-color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-stack__lang { color: #FFFFFF; }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-stack__base { color: var(--slate); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-stack__badge { background: rgba(0,224,184,0.12); color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue { background: rgba(255,255,255,0.04); border-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue:hover { border-color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue[open] { border-color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue__summary { color: #FFFFFF; }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue__body { border-top-color: var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue__body p { color: var(--neutral); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue__body code { background: rgba(255,255,255,0.06); color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue__tag--sw { background: rgba(107,71,240,0.18); color: #B59CFF; border-color: rgba(107,71,240,0.30); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue__tag--config { background: rgba(0,224,184,0.12); color: var(--secondary); border-color: rgba(0,224,184,0.22); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-issue[open] .sf-issue__chev { color: var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-step__num { box-shadow: 0 0 0 5px var(--tertiary), 0 4px 10px rgba(0,0,0,.40); }

@media (prefers-color-scheme: dark) {
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc {
    --primary: #034860; --secondary: #00E0B8; --tertiary: #1B2D45;
    --neutral: #E8F5F0; --white: #FFFFFF; --light: #14222F;
    --line: #2A3A4D; --slate-light: #94A8B5; --slate: #B5C3CE;
    --deep-ink: #E8F5F0;
    --success-bg: #052E1B; --warning-bg: #2A1F05;
    --danger-bg: #2A0E0E; --info-bg: #0A2E3F;
    --success-text: #34D399; --warning-text: #FBBF24;
    --danger-text: #F87171; --info-text: #5CD3EE;
  --ai-iris: #B59CFF; --ai-mist: #1F1A33;
    color: var(--deep-ink);
  }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc h1,
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc h3,
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc h4,
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc strong { color: #FFFFFF; }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc h2 { color: var(--secondary); border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc p,
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc li,
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-aeo__text,
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-callout__content { color: var(--neutral); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc a { color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc code { background: rgba(255,255,255,0.06); color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc hr { background: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc ul li::marker { color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-meta__badge { background: rgba(255,255,255,0.06); color: var(--secondary); border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-aeo { background: rgba(255,255,255,0.04); border-color: var(--line); border-left-color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-aeo__label { color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-aeo__text strong { color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-hero { background: rgba(255,255,255,0.04); border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-hero p.sf-hero__title,
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-hero .sf-hero__title { color: #FFFFFF; }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-hero p.sf-hero__lead,
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-hero .sf-hero__lead { color: var(--neutral); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-toc { background: rgba(255,255,255,0.04); border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-toc__item { background: transparent; color: var(--neutral) !important; border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-toc__item:hover { background: var(--secondary); color: var(--tertiary) !important; border-color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-toc__item:hover .sf-toc__num { color: var(--tertiary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-card { background: rgba(255,255,255,0.04); border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-card:hover { border-color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-card__head { border-bottom-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-card__icon { background: rgba(0,224,184,0.10); color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-card__title { color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-row { border-bottom-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-row__key { color: #FFFFFF; }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-row__val { color: var(--neutral); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-spec-row__val code { background: rgba(255,255,255,0.06); color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-pill { background: rgba(255,255,255,0.06); color: var(--neutral); border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-path { background: rgba(255,255,255,0.04); border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-path__list li { color: var(--neutral); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-path__list li::before { background: rgba(0,224,184,0.14); background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2300E0B8' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>"); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-path__foot { border-top-color: var(--line); color: var(--slate); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-path__foot strong { color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-stack { background: rgba(255,255,255,0.04); border-color: var(--line); color: var(--neutral) !important; }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-stack:hover { border-color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-stack__lang { color: #FFFFFF; }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-stack__base { color: var(--slate); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-stack__badge { background: rgba(0,224,184,0.12); color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue { background: rgba(255,255,255,0.04); border-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue:hover { border-color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue[open] { border-color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue__summary { color: #FFFFFF; }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue__body { border-top-color: var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue__body p { color: var(--neutral); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue__body code { background: rgba(255,255,255,0.06); color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue__tag--sw { background: rgba(107,71,240,0.18); color: #B59CFF; border-color: rgba(107,71,240,0.30); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue__tag--config { background: rgba(0,224,184,0.12); color: var(--secondary); border-color: rgba(0,224,184,0.22); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-issue[open] .sf-issue__chev { color: var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-step__num { box-shadow: 0 0 0 5px var(--tertiary), 0 4px 10px rgba(0,0,0,.40); }
}

/* ════════════════════════════════════════════════════════════════════════
   RESPONSIVE
═════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  .sf-doc h1 { font-size: 2.25rem; }
  .sf-doc h2 { font-size: 1.25rem; margin-top: 48px; }
  .sf-doc .sf-hero { padding: var(--md); }
  .sf-doc .sf-hero p.sf-hero__title,
  .sf-doc .sf-hero .sf-hero__title { font-size: 1.75rem; }
  .sf-doc .sf-aeo { padding: var(--md) var(--lg); }
  .sf-doc .sf-aeo__label { font-size: 1.125rem; }
  .sf-doc .sf-toc { gap: 4px; padding: 10px; }
  .sf-doc .sf-spec-card { padding: 16px 18px; }
  .sf-doc .sf-spec-row { grid-template-columns: 1fr; gap: 4px; padding: 12px 0; }
  .sf-doc .sf-spec-row__key { font-size: 0.875rem; }
  .sf-doc .sf-spec-row__val { font-size: 0.9375rem; }
  .sf-doc .sf-paths { grid-template-columns: 1fr; }
  .sf-doc .sf-stack-grid { grid-template-columns: 1fr; }
  .sf-doc .sf-step-list::before { left: 19px; top: 40px; }
  .sf-doc .sf-step__header { grid-template-columns: 40px 1fr; gap: 0 16px; }
  .sf-doc .sf-step__num { width: 40px; height: 40px; font-size: 0.9375rem; }
  .sf-doc .sf-step__title { font-size: 1.25rem; line-height: 1.2; }
  .sf-doc .sf-step__body { padding-left: 56px; }
}
@media (max-width: 480px) {
  .sf-doc .sf-hero p.sf-hero__title,
  .sf-doc .sf-hero .sf-hero__title { font-size: 1.5rem; }
  .sf-doc .sf-step__body { padding-left: 0; }
}

/* ═══════════════════════════════════════════════════════════════
   TABLES · .sf-table inside .sf-table-wrap (core component)
   Real <table> markup needs the ReadMe reset at the end of this
   section: ReadMe ships table { display:block; width:max-content }.
   ═══════════════════════════════════════════════════════════════ */
.sf-doc .sf-table-wrap { display:block; width:100%; overflow-x:auto; border:1px solid var(--line); border-radius:var(--r-lg); background:var(--white); margin:16px 0 24px; }
.sf-doc .sf-table { width:100% !important; border-collapse:separate; border-spacing:0; font-size:0.9375rem; margin:0 !important; }
.sf-doc .sf-table thead th { padding:12px 16px; font-family:var(--font-sans); font-size:0.8125rem; font-weight:700; letter-spacing:0; text-transform:none; color:var(--slate); background:var(--light); text-align:left; border-bottom:1px solid var(--line); white-space:nowrap; }
.sf-doc .sf-table td { padding:12px 16px; border-bottom:1px solid var(--line); font-size:0.9375rem; color:var(--deep-ink); vertical-align:top; }
.sf-doc .sf-table tbody th, .sf-doc .sf-table th[scope="row"] { padding:12px 16px; border-bottom:1px solid var(--line); font-family:var(--font-sans); font-size:0.9375rem; font-weight:600; text-align:left; color:var(--primary); background:var(--light); width:32%; vertical-align:top; white-space:normal; }
.sf-doc .sf-table tr:last-child td, .sf-doc .sf-table tr:last-child th { border-bottom:0; }
.sf-doc .sf-table code { font-size:0.8125rem; }
.sf-doc .sf-table--data { table-layout:fixed !important; width:100% !important; min-width:100% !important; }
.sf-doc .sf-table--data thead th { white-space:normal !important; }
.sf-doc .sf-table--data td:nth-child(1), .sf-doc .sf-table--data th:nth-child(1) { width:22% !important; }
.sf-doc .sf-table--data td:nth-child(2), .sf-doc .sf-table--data th:nth-child(2) { width:39% !important; }
.sf-doc .sf-table--data td:nth-child(3), .sf-doc .sf-table--data th:nth-child(3) { width:39% !important; }
.sf-doc .sf-table--data tbody tr td:first-child { font-weight:600; color:var(--primary); }
.sf-doc .sf-table-wrap { display:block !important; width:100% !important; overflow-x:auto !important; }

[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-table-wrap { background:rgba(255,255,255,0.04); border-color:var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-table thead th { background:rgba(255,255,255,0.06); color:var(--slate); border-bottom-color:var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-table td { color:var(--neutral); border-bottom-color:var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-table tbody th, [data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-table th[scope="row"] { background:rgba(255,255,255,0.06); color:var(--secondary); border-bottom-color:var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-table--data tbody tr td:first-child { color:var(--secondary); }
@media (prefers-color-scheme: dark) {
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-table-wrap { background:rgba(255,255,255,0.04); border-color:var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-table thead th { background:rgba(255,255,255,0.06); color:var(--slate); border-bottom-color:var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-table td { color:var(--neutral); border-bottom-color:var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-table tbody th, :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-table th[scope="row"] { background:rgba(255,255,255,0.06); color:var(--secondary); border-bottom-color:var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-table--data tbody tr td:first-child { color:var(--secondary); }
}
@media (max-width: 640px) {
  .sf-doc .sf-table td, .sf-doc .sf-table th { padding:9px 12px; font-size:0.8125rem; }
}

/* ReadMe table reset */
.markdown-body table { margin-bottom:0 !important; }
.sf-doc table.sf-table { display:table !important; width:100% !important; table-layout:auto !important; }
.sf-doc table.sf-table.sf-table--data { table-layout:fixed !important; }
/* Modifiers used by several tutorials but missing from the base lineage */
.sf-doc .sf-step__hint { font-size: 0.875rem; color: var(--slate); margin: 6px 0 0; }
.sf-doc .sf-terminal__dot--r { background: #ff5f56; }
.sf-doc .sf-terminal__dot--y { background: #ffbd2e; }
.sf-doc .sf-terminal__dot--g { background: #27c93f; }
.sf-doc .sf-terminal__lang { margin-left: auto; font-family: var(--font-mono) !important; font-size: 0.6875rem !important; color: #c9d1d9 !important; background: rgba(255,255,255,0.06); padding: 2px 8px; border-radius: var(--r-sm); font-weight: 600; letter-spacing: 0.05em; }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-step__hint { color: var(--slate); }
@media (prefers-color-scheme: dark) {
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-step__hint { color: var(--slate); }
}
/* ════════════════════════════════════════════════════════════════════════
   RELATED GUIDES — shared discovery footer
═════════════════════════════════════════════════════════════════════════ */
.sf-doc .sf-related { position:relative; margin:56px 0 8px; padding:28px; border:1px solid var(--line); border-radius:var(--r-2xl); background:linear-gradient(135deg, rgba(3,72,96,.055) 0%, rgba(0,224,184,.075) 100%); overflow:hidden; }
.sf-doc .sf-related::before { content:''; position:absolute; width:220px; height:220px; right:-90px; top:-120px; border-radius:50%; background:radial-gradient(circle, rgba(0,224,184,.18) 0%, rgba(0,224,184,0) 70%); pointer-events:none; }
.sf-doc .sf-related__head { position:relative; display:grid; grid-template-columns:minmax(0, .8fr) minmax(260px, 1.2fr); gap:24px; align-items:end; margin-bottom:22px; }
.sf-doc .sf-related__eyebrow { display:block; margin-bottom:7px; font-family:var(--font-mono); font-size:.6875rem; font-weight:500; line-height:1.4; letter-spacing:.13em; text-transform:uppercase; color:var(--primary); }
.sf-doc .sf-related__title { margin:0; padding:0; border:0; font-size:1.625rem; line-height:1.15; color:var(--deep-ink); }
.sf-doc .sf-related__intro { margin:0; color:var(--slate); font-size:.9375rem; line-height:1.55; }
.sf-doc .sf-related__grid { position:relative; display:grid; grid-template-columns:repeat(3, minmax(0, 1fr)); gap:12px; }
.sf-doc .sf-related__card { position:relative; display:grid; grid-template-rows:auto auto auto 1fr auto; min-width:0; min-height:190px; padding:18px; background:var(--white); border:1px solid var(--line); border-radius:var(--r-xl); color:var(--deep-ink); text-decoration:none !important; box-shadow:var(--shadow-sm); overflow:hidden; transition:transform .2s ease, border-color .2s ease, box-shadow .2s ease; }
.sf-doc .sf-related__card::before { content:''; position:absolute; inset:0 0 auto; height:3px; background:linear-gradient(90deg, var(--primary), var(--secondary)); transform:scaleX(0); transform-origin:left; transition:transform .2s ease; }
.sf-doc .sf-related__card:hover { color:var(--deep-ink); border-color:rgba(3,72,96,.38); box-shadow:0 12px 28px rgba(3,72,96,.12); transform:translateY(-3px); }
.sf-doc .sf-related__card:hover::before { transform:scaleX(1); }
.sf-doc .sf-related__icon { display:flex; align-items:center; justify-content:center; width:38px; height:38px; margin-bottom:16px; border-radius:var(--r-lg); background:var(--neutral); color:var(--primary); }
.sf-doc .sf-related__icon svg { width:19px; height:19px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.sf-doc .sf-related__kicker { margin-bottom:5px; font-family:var(--font-mono); font-size:.625rem; font-weight:500; line-height:1.4; letter-spacing:.1em; text-transform:uppercase; color:var(--slate); }
.sf-doc .sf-related__card-title { display:block; margin-bottom:7px; color:var(--deep-ink); font-family:var(--font-sans); font-size:1rem; font-weight:700; line-height:1.25; }
.sf-doc .sf-related__desc { color:var(--slate); font-size:.8125rem; line-height:1.5; }
.sf-doc .sf-related__action { display:flex; align-items:center; gap:6px; margin-top:16px; color:var(--primary); font-family:var(--font-sans); font-size:.75rem; font-weight:700; }
.sf-doc .sf-related__action svg { width:14px; height:14px; fill:none; stroke:currentColor; stroke-width:2; transition:transform .2s ease; }
.sf-doc .sf-related__card:hover .sf-related__action svg { transform:translateX(3px); }
.sf-doc .sf-related__grid--four { grid-template-columns:repeat(2, minmax(0, 1fr)); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-related { background:linear-gradient(135deg, rgba(0,224,184,.08) 0%, rgba(0,166,200,.045) 100%); border-color:var(--line); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-related__eyebrow, [data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-related__action { color:var(--secondary); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-related__card { background:rgba(255,255,255,.045); border-color:var(--line); box-shadow:none; }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-related__card:hover { border-color:rgba(0,224,184,.5); box-shadow:0 12px 28px rgba(0,0,0,.28); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-related__icon { background:rgba(0,224,184,.1); color:var(--secondary); }
@media (prefers-color-scheme: dark) {
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-related { background:linear-gradient(135deg, rgba(0,224,184,.08) 0%, rgba(0,166,200,.045) 100%); border-color:var(--line); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-related__eyebrow, :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-related__action { color:var(--secondary); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-related__card { background:rgba(255,255,255,.045); border-color:var(--line); box-shadow:none; }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-related__card:hover { border-color:rgba(0,224,184,.5); box-shadow:0 12px 28px rgba(0,0,0,.28); }
  :is(html:not([data-color-mode="light"]):where(:not(:has(.rm-ReadMe[data-color-mode="light"]))),:where(.rm-ReadMe:is([data-color-mode="system"],[data-color-mode="dark"]))) .sf-doc .sf-related__icon { background:rgba(0,224,184,.1); color:var(--secondary); }
}
@media (max-width: 820px) { .sf-doc .sf-related__grid { grid-template-columns:1fr 1fr; } }
@media (max-width: 640px) {
  .sf-doc .sf-related { margin-top:40px; padding:22px 18px; }
  .sf-doc .sf-related__head { grid-template-columns:1fr; gap:10px; }
  .sf-doc .sf-related__grid { grid-template-columns:1fr; }
  .sf-doc .sf-related__card { min-height:0; }
}
/* ══ END CANONICAL TUTORIAL STYLESHEET ══ */
</style>

<div class="sf-doc">

  
  <header class="sf-hero">
    <div class="sf-hero__copy">
      <p class="sf-hero__title">Deploy a WireGuard VPN on ALPON</p>
      <p class="sf-hero__lead">
        Turn your <strong>ALPON X5 AI</strong> or <strong>ALPON X4</strong> into a private <strong>WireGuard</strong>
        VPN server. Whether you are securing your internet connection or creating a private network for remote
        access, this guide walks you through everything — from generating client keys and building the server
        container to deploying it through <strong>ALPON Cloud</strong> and connecting your devices.
      </p>
      <div class="sf-hero__chips">
        <span class="sf-pill sf-pill--alpon-x5">ALPON X5 AI</span>
        <span class="sf-pill sf-pill--alpon-x4">ALPON X4</span>
        <span class="sf-pill">WireGuard</span>
        <span class="sf-pill">VPN</span>
      </div>
    </div>
  </header>


  
  <div class="sf-meta">
    <span class="sf-meta__badge">ALPON</span>
    <span class="sf-meta__sep">·</span>
    <span class="sf-meta__badge sf-meta__badge--mint">Tutorial</span>
    <span class="sf-meta__sep">·</span>
    <span>Containers</span>
    <span class="sf-meta__sep">·</span>
    <span>Networking</span>
  </div>


  
  <div class="sf-aeo">
    <span class="sf-aeo__label">How do I deploy a WireGuard VPN on ALPON?</span>
    <p class="sf-aeo__text">
      Generate a WireGuard key pair and pre-shared key on the client, build the <code>private-network</code> server
      image from the Dockerfile in this guide and push it to your <strong>Sixfab Container Registry</strong>, then
      use the <strong>Applications → Deploy</strong> panel on <strong>ALPON Cloud</strong> to launch it on your
      <strong>ALPON X5 AI</strong> or <strong>ALPON X4</strong> with the <code>CLIENT_PUBLIC_KEY</code> and
      <code>CLIENT_PSK</code> environment variables, <strong>Host Network</strong>, and <strong>Privileged</strong>
      enabled. Read the server's public key from the container shell, write it into <code>client.conf</code>, and
      connect from the WireGuard app on port <code>22222</code>.
    </p>
  </div>


  
  <nav class="sf-toc" aria-label="On this page">
    <span class="sf-toc__label">In this guide</span>
    <a class="sf-toc__item" href="#overview"><span class="sf-toc__num">01</span>Overview</a>
    <a class="sf-toc__item" href="#step-keys"><span class="sf-toc__num">02</span>Generate client keys</a>
    <a class="sf-toc__item" href="#step-image"><span class="sf-toc__num">03</span>Add the container</a>
    <a class="sf-toc__item" href="#step-deploy"><span class="sf-toc__num">04</span>Deploy on ALPON</a>
    <a class="sf-toc__item" href="#step-server-key"><span class="sf-toc__num">05</span>Get the server key</a>
    <a class="sf-toc__item" href="#step-client"><span class="sf-toc__num">06</span>Configure the client</a>
    <a class="sf-toc__item" href="#step-connect"><span class="sf-toc__num">07</span>Connect</a>
    <a class="sf-toc__item" href="#step-verify"><span class="sf-toc__num">08</span>Test the connection</a>
  </nav>


  
  <h2 id="overview">Overview</h2>

  <p><strong>WireGuard</strong> is a fast, modern VPN protocol with a small, auditable code base. Running it as a
  container on an <strong>ALPON X5 AI</strong> or <strong>ALPON X4</strong> gives you a powerful and flexible VPN
  endpoint on your own network: the client generates its own keys and configuration, while the server container
  uses the keys you pass in as environment variables to establish the tunnel.</p>

  <p>The steps below are identical on ALPON X4 and ALPON X5 AI. For protocol details and client apps, see the
  <a href="https://www.wireguard.com/" target="_blank" rel="noopener">official WireGuard documentation</a>.</p>

  <div class="sf-callout sf-callout--info">
    <div class="sf-callout__header">
      <svg class="sf-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
      <span>Before you start</span>
    </div>
    <div class="sf-callout__content">
      <p>You need an ALPON X5 AI or ALPON X4 registered on
      <a href="https://connect.sixfab.com" target="_blank" rel="noopener">ALPON Cloud</a>, and Docker installed on
      your build machine to build and push the image — grab it from the
      <a href="https://www.docker.com/" target="_blank" rel="noopener">official Docker website</a> if you don't have
      it yet. New to container deployment? Start with
      <a href="https://docs.sixfab.com/docs/alpon-app-containerization-dockerfile-templates" target="_blank" rel="noopener">Containerize Apps for ALPON</a>.</p>
      <p><strong>Forward a port on your router:</strong> if your ALPON is behind a router (like most home setups),
      forward port <code>22222</code> from your router to the ALPON. Check your router's manual for instructions —
      this makes the VPN reachable from outside your network.</p>
      <p><strong>Set up a domain name:</strong> to reach your VPN from outside, you need a domain name or a dynamic
      DNS (DynDNS) service so the connection keeps working when your public IP address changes. Popular options are
      <a href="https://www.duckdns.org/" target="_blank" rel="noopener">DuckDNS</a> and
      <a href="https://www.noip.com/" target="_blank" rel="noopener">No-IP</a>.</p>
    </div>
  </div>

  <div class="sf-callout sf-callout--warning">
    <div class="sf-callout__header">
      <svg class="sf-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>
      <span>Reserved subnets</span>
    </div>
    <div class="sf-callout__content">
      <p>Avoid using the subnets <code>100.0.0.0/16</code> and <code>10.42.0.0/24</code> for your VPN, as these are
      reserved for the ALPON's default applications. This guide uses custom subnets instead to keep things running
      smoothly.</p>
    </div>
  </div>


  
  <ol class="sf-step-list">

    
    <li class="sf-step" id="step-keys">
      <div class="sf-step__header">
        <div class="sf-step__num">1</div>
        <h3 class="sf-step__title">Generate your client keys</h3>
      </div>
      <div class="sf-step__body">
        <p>The client creates its own private key, public key, and pre-shared key (PSK). Follow the instructions for
        your operating system; whichever you use, you end up with the same three files.</p>

        <h4>For Linux users</h4>
        <p>Install the WireGuard tools, then generate the private and public keys and the pre-shared key:</p>

        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot sf-terminal__dot--r"></span>
            <span class="sf-terminal__dot sf-terminal__dot--y"></span>
            <span class="sf-terminal__dot sf-terminal__dot--g"></span>
            <span class="sf-terminal__title">bash · Linux</span>
          </div>
          <div class="sf-terminal__body">
<pre><span class="t-dim"># Install WireGuard tools</span>
sudo apt-get update
sudo apt-get install wireguard-tools

<span class="t-dim"># Generate private key and public key</span>
wg genkey | tee client-private.key | wg pubkey &gt; client-public.key

<span class="t-dim"># Generate pre-shared key (PSK)</span>
wg genpsk &gt; client.psk</pre>
          </div>
        </div>

        <h4>For macOS users</h4>
        <p>Install the WireGuard tools with Homebrew (a macOS package manager), then run the same key commands:</p>

        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot sf-terminal__dot--r"></span>
            <span class="sf-terminal__dot sf-terminal__dot--y"></span>
            <span class="sf-terminal__dot sf-terminal__dot--g"></span>
            <span class="sf-terminal__title">zsh · macOS</span>
          </div>
          <div class="sf-terminal__body">
<pre><span class="t-dim"># Install WireGuard tools</span>
brew install wireguard-tools

<span class="t-dim"># Generate private key and public key</span>
wg genkey | tee client-private.key | wg pubkey &gt; client-public.key

<span class="t-dim"># Generate pre-shared key (PSK)</span>
wg genpsk &gt; client.psk</pre>
          </div>
        </div>

        <h4>For Windows users</h4>
        <p>Windows doesn't support the WireGuard tools directly in the command line, but you have two easy
        options.</p>

        <p><strong>Option 1 — use the WireGuard app.</strong> Download and install the Windows application from the
        <a href="https://www.wireguard.com/install/" target="_blank" rel="noopener">WireGuard official website</a>,
        then open a command prompt and run:</p>

        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot sf-terminal__dot--r"></span>
            <span class="sf-terminal__dot sf-terminal__dot--y"></span>
            <span class="sf-terminal__dot sf-terminal__dot--g"></span>
            <span class="sf-terminal__title">cmd · Windows</span>
          </div>
          <div class="sf-terminal__body">
<pre><span class="t-dim">:: Generate private key and public key</span>
wg genkey &gt; client-private.key
type client-private.key | wg pubkey &gt; client-public.key

<span class="t-dim">:: Generate pre-shared key (PSK)</span>
wg genpsk &gt; client.psk</pre>
          </div>
        </div>

        <p><strong>Option 2 — use WSL (Windows Subsystem for Linux).</strong> If you prefer, install WSL and follow
        the Linux steps above.</p>

        <h4>What you'll have</h4>
        <ul>
          <li><code>client-private.key</code>: the client's private key (keep it safe!).</li>
          <li><code>client-public.key</code>: the client's public key (share this with the server).</li>
          <li><code>client.psk</code>: the pre-shared key (PSK).</li>
        </ul>
      </div>
    </li>


    
    <li class="sf-step" id="step-image">
      <div class="sf-step__header">
        <div class="sf-step__num">2</div>
        <h3 class="sf-step__title">Add the WireGuard server container</h3>
      </div>
      <div class="sf-step__body">
        <p>Now configure the ALPON as your VPN server using a container. The Dockerfile below sets up a WireGuard
        server that generates its own key pair at build time and reads the client's public key
        (<code>CLIENT_PUBLIC_KEY</code>) and pre-shared key (<code>CLIENT_PSK</code>) from environment variables.
        Save it as <code>Dockerfile</code>:</p>

        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot sf-terminal__dot--r"></span>
            <span class="sf-terminal__dot sf-terminal__dot--y"></span>
            <span class="sf-terminal__dot sf-terminal__dot--g"></span>
            <span class="sf-terminal__title">Dockerfile</span>
          </div>
          <div class="sf-terminal__body">
<pre>FROM alpine:latest

RUN apk update &amp;&amp; apk add wireguard-tools wireguard-tools-wg-quick bash gettext
RUN mkdir -p /etc/wireguard
WORKDIR /etc/wireguard
RUN umask 077

# Generate server keys
RUN wg genkey | tee server.key | wg pubkey &gt; server.pub

# Create WireGuard server configuration template
RUN echo "[Interface]" &gt; /etc/wireguard/wg0.conf.template &amp;&amp; \\
    echo "Address = 10.200.0.1/24, fd09:4722::1/64" &gt;&gt; /etc/wireguard/wg0.conf.template &amp;&amp; \\
    echo "ListenPort = 22222" &gt;&gt; /etc/wireguard/wg0.conf.template &amp;&amp; \\
    echo "PrivateKey = $(cat server.key)" &gt;&gt; /etc/wireguard/wg0.conf.template &amp;&amp; \\
    echo "[Peer]" &gt;&gt; /etc/wireguard/wg0.conf.template &amp;&amp; \\
    echo "PublicKey = \\\${CLIENT_PUBLIC_KEY}" &gt;&gt; /etc/wireguard/wg0.conf.template &amp;&amp; \\
    echo "PresharedKey = \\\${CLIENT_PSK}" &gt;&gt; /etc/wireguard/wg0.conf.template &amp;&amp; \\
    echo "AllowedIPs = 10.200.0.2/32, fd09:4722::2/128" &gt;&gt; /etc/wireguard/wg0.conf.template

# Set the command to generate the config and start WireGuard
CMD sh -c "envsubst &lt; /etc/wireguard/wg0.conf.template &gt; /etc/wireguard/wg0.conf &amp;&amp; \\
           wg-quick up /etc/wireguard/wg0.conf &amp;&amp; \\
           sleep infinity"</pre>
          </div>
        </div>

        <p>Open a terminal on your computer, navigate to the folder with your Dockerfile, and build the
        <code>arm64</code> image:</p>

        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot sf-terminal__dot--r"></span>
            <span class="sf-terminal__dot sf-terminal__dot--y"></span>
            <span class="sf-terminal__dot sf-terminal__dot--g"></span>
            <span class="sf-terminal__title">bash · build the image</span>
          </div>
          <div class="sf-terminal__body">
<pre>docker buildx build --platform linux/arm64 -t private-network:latest ./</pre>
          </div>
        </div>

        <p>Then log in to <a href="https://connect.sixfab.com/#/registry" target="_blank" rel="noopener">Sixfab
        Registry</a>, click <strong>+ Add Container</strong>, and follow the prompts to push the image.</p>

        <div class="sf-callout sf-callout--info">
          <div class="sf-callout__header">
            <svg class="sf-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"/></svg>
            <span>Pushing images to the Sixfab Container Registry</span>
          </div>
          <div class="sf-callout__content">
            <p>For the full walkthrough of tagging and pushing an image, see
            <a href="https://docs.sixfab.com/docs/alpon-update-containers-from-sixfab-registry" target="_blank" rel="noopener">Update Containers from the Sixfab Container Registry</a>
            and the <a href="https://docs.sixfab.com/docs/alpon-deploy-applications" target="_blank" rel="noopener">Deploy Applications</a> page.</p>
          </div>
        </div>
      </div>
    </li>


    
    <li class="sf-step" id="step-deploy">
      <div class="sf-step__header">
        <div class="sf-step__num">3</div>
        <h3 class="sf-step__title">Deploy the container on ALPON</h3>
      </div>
      <div class="sf-step__body">
        <p>Once the image is in the Sixfab Container Registry, open your device in
        <a href="https://connect.sixfab.com" target="_blank" rel="noopener">ALPON Cloud</a>, go to the
        <strong>Applications</strong> section, and click <strong>+ Deploy</strong>. In the
        <strong>Deploy Container</strong> window, use these settings:</p>

        <div class="sf-spec-card">
          <div class="sf-spec-card__body">
            <div class="sf-spec-row">
              <span class="sf-spec-row__key"><code>Container Name</code></span>
              <span class="sf-spec-row__val"><code>private-network</code></span>
            </div>
            <div class="sf-spec-row">
              <span class="sf-spec-row__key"><code>Image</code></span>
              <span class="sf-spec-row__val">The <code>private-network</code> image and tag you pushed to the Sixfab Container Registry.</span>
            </div>
            <div class="sf-spec-row">
              <span class="sf-spec-row__key"><code>Environment</code></span>
              <span class="sf-spec-row__val">Click <strong>+ Add More</strong> and add the two variables in the table below with your client's values from step 1.</span>
            </div>
            <div class="sf-spec-row">
              <span class="sf-spec-row__key"><code>Enable Host Network</code></span>
              <span class="sf-spec-row__val">Turn this <strong>on</strong>.</span>
            </div>
            <div class="sf-spec-row">
              <span class="sf-spec-row__key"><code>Privileged</code></span>
              <span class="sf-spec-row__val">Turn this <strong>on</strong> so the container can create the <code>wg0</code> interface.</span>
            </div>
          </div>
        </div>

        <div class="sf-table-wrap">
          <table class="sf-table">
            <thead>
              <tr><th scope="col">Key</th><th scope="col">Value</th></tr>
            </thead>
            <tbody>
              <tr><td><code>CLIENT_PUBLIC_KEY</code></td><td>Contents of <code>client-public.key</code></td></tr>
              <tr><td><code>CLIENT_PSK</code></td><td>Contents of <code>client.psk</code></td></tr>
            </tbody>
          </table>
        </div>

        <p>Click <strong>+ Deploy</strong> to start the WireGuard server on the device.</p>
      </div>
    </li>


    
    <li class="sf-step" id="step-server-key">
      <div class="sf-step__header">
        <div class="sf-step__num">4</div>
        <h3 class="sf-step__title">Get the server's public key</h3>
      </div>
      <div class="sf-step__body">
        <p>After the <code>private-network</code> container is deployed, open its shell from ALPON Cloud using the
        shell button shown below.</p>

        <span class="sf-fig-anchor" id="fig-shell"></span>
        <figure class="sf-figure">
          <a href="#lb-shell" aria-label="Open enlarged image">
            <img src="https://files.readme.io/7f353ec6f2ffe4b0d25d9d2c9fd94768566f274a1b5df9491921c6bc88d51f4a-image.png" alt="ALPON Cloud Applications section with the shell button for the private-network container highlighted" loading="lazy">
          </a>
          <figcaption>The container shell button in the Applications section of ALPON Cloud.</figcaption>
        </figure>

        <p>In the container shell, print the server's public key and copy the output — this is your
        <code>server-public-key</code>:</p>

        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot sf-terminal__dot--r"></span>
            <span class="sf-terminal__dot sf-terminal__dot--y"></span>
            <span class="sf-terminal__dot sf-terminal__dot--g"></span>
            <span class="sf-terminal__title">container shell · private-network</span>
          </div>
          <div class="sf-terminal__body">
<pre>cat server.pub</pre>
          </div>
        </div>
      </div>
    </li>


    
    <li class="sf-step" id="step-client">
      <div class="sf-step__header">
        <div class="sf-step__num">5</div>
        <h3 class="sf-step__title">Configure your client</h3>
      </div>
      <div class="sf-step__body">
        <p>The client needs its own configuration file, <code>client.conf</code>, built from the server's public key
        and the pre-shared key. Here is an example configuration:</p>

        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot sf-terminal__dot--r"></span>
            <span class="sf-terminal__dot sf-terminal__dot--y"></span>
            <span class="sf-terminal__dot sf-terminal__dot--g"></span>
            <span class="sf-terminal__title">client.conf</span>
          </div>
          <div class="sf-terminal__body">
<pre>[Interface]
Address = 10.200.0.2/32, fd09:4722::2/128
DNS = 1.1.1.1
PrivateKey = <span class="t-yel">&lt;client-private-key&gt;</span>

[Peer]
PublicKey = <span class="t-yel">&lt;server-public-key&gt;</span>
PresharedKey = <span class="t-yel">&lt;client-psk&gt;</span>
AllowedIPs = 10.200.0.1/32, fd09:4722::1/128
Endpoint = <span class="t-yel">&lt;your-public-ip-or-domain&gt;</span>:22222
PersistentKeepalive = 25</pre>
          </div>
        </div>

        <p>Replace the following placeholders:</p>
        <ul>
          <li><code>&lt;client-private-key&gt;</code>: the client's private key (from <code>client-private.key</code>).</li>
          <li><code>&lt;server-public-key&gt;</code>: the server's public key (from <code>server.pub</code> inside the container).</li>
          <li><code>&lt;client-psk&gt;</code>: the pre-shared key (from <code>client.psk</code>).</li>
          <li><code>&lt;your-public-ip-or-domain&gt;</code>: the public IP or domain of the server.</li>
        </ul>
      </div>
    </li>


    
    <li class="sf-step" id="step-connect">
      <div class="sf-step__header">
        <div class="sf-step__num">6</div>
        <h3 class="sf-step__title">Connect to your VPN</h3>
      </div>
      <div class="sf-step__body">
        <p><strong>Import the config.</strong> On the client device, open the WireGuard app, click
        <strong>+ → Import from file</strong>, and select <code>client.conf</code>.</p>
        <p><strong>Activate the VPN.</strong> In the WireGuard app, toggle the switch to connect, then verify the
        connection by checking the status in the app.</p>
      </div>
    </li>


    
    <li class="sf-step" id="step-verify">
      <div class="sf-step__header">
        <div class="sf-step__num">7</div>
        <h3 class="sf-step__title">Test your connection</h3>
      </div>
      <div class="sf-step__body">
        <p>Make sure everything works by deploying a simple Nginx server on the ALPON and reaching it through the
        tunnel. In the <strong>Applications</strong> section, click <strong>+ Deploy</strong> and use these
        settings:</p>

        <div class="sf-spec-card">
          <div class="sf-spec-card__body">
            <div class="sf-spec-row">
              <span class="sf-spec-row__key"><code>Container Name</code></span>
              <span class="sf-spec-row__val"><code>nginx-test</code></span>
            </div>
            <div class="sf-spec-row">
              <span class="sf-spec-row__key"><code>Image</code></span>
              <span class="sf-spec-row__val">Turn on <strong>“I would like to use my own container path”</strong> and enter <code>nginx:latest</code>.</span>
            </div>
            <div class="sf-spec-row">
              <span class="sf-spec-row__key"><code>Ports</code></span>
              <span class="sf-spec-row__val">Click <strong>+ Add More</strong> and add the port mapping in the table below.</span>
            </div>
          </div>
        </div>

        <div class="sf-table-wrap">
          <table class="sf-table">
            <thead>
              <tr><th scope="col">From</th><th scope="col">To</th></tr>
            </thead>
            <tbody>
              <tr><td><code>31080</code></td><td><code>80</code></td></tr>
            </tbody>
          </table>
        </div>

        <p>Click <strong>Deploy</strong> to start the application, then open the server's VPN address in a browser
        on the connected client:</p>

        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot sf-terminal__dot--r"></span>
            <span class="sf-terminal__dot sf-terminal__dot--y"></span>
            <span class="sf-terminal__dot sf-terminal__dot--g"></span>
            <span class="sf-terminal__title">browser · Nginx over the VPN</span>
          </div>
          <div class="sf-terminal__body">
<pre>http://10.200.0.1:31080</pre>
          </div>
        </div>

        <p>If the client is connected successfully, the Nginx welcome screen appears in your browser.</p>
      </div>
    </li>

  </ol>


  
  <div class="sf-callout sf-callout--warning">
    <div class="sf-callout__header">
      <svg class="sf-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>
      <span>Troubleshooting: container crashes and <code>wg0</code> already exists</span>
    </div>
    <div class="sf-callout__content">
      <p>If the container crashes or is restarted, it may fail with <code>RTNETLINK answers: File exists</code>
      because the <code>wg0</code> interface is still defined on the host. This happens when the container is
      stopped abruptly and the interface is not cleaned up. To resolve it, delete the interface manually on the
      ALPON:</p>
      <div class="sf-terminal">
        <div class="sf-terminal__header">
          <span class="sf-terminal__dot sf-terminal__dot--r"></span>
          <span class="sf-terminal__dot sf-terminal__dot--y"></span>
          <span class="sf-terminal__dot sf-terminal__dot--g"></span>
          <span class="sf-terminal__title">bash · on the ALPON</span>
        </div>
        <div class="sf-terminal__body">
<pre>sudo ip link del wg0</pre>
        </div>
      </div>
    </div>
  </div>


  
  <div class="sf-callout sf-callout--success">
    <div class="sf-callout__header">
      <svg class="sf-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
      <span>Ready when…</span>
    </div>
    <div class="sf-callout__content">
      <ul>
        <li>The <code>private-network</code> container shows as running in the Applications section.</li>
        <li>The WireGuard app on the client reports the tunnel as connected.</li>
        <li>The Nginx welcome page loads at <code>http://10.200.0.1:31080</code> from the client.</li>
      </ul>
      <p>You now have a private VPN running inside a container on the ALPON. The client handles its own key
      generation and configuration, while the server uses the provided keys to establish the connection. For further
      assistance, refer to the <a href="https://www.wireguard.com/" target="_blank" rel="noopener">WireGuard
      documentation</a>.</p>
    </div>
  </div>


  
  <div class="sf-lightbox" id="lb-shell" role="dialog" aria-label="Enlarged image: ALPON Cloud Applications section">
    <a class="sf-lightbox__backdrop" href="#fig-shell" aria-label="Close"></a>
    <a class="sf-lightbox__close" href="#fig-shell" aria-label="Close enlarged image">
      <svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
    </a>
    <div class="sf-lightbox__inner">
      <a class="sf-lightbox__img-wrap" href="#fig-shell" aria-label="Close">
        <img src="https://files.readme.io/7f353ec6f2ffe4b0d25d9d2c9fd94768566f274a1b5df9491921c6bc88d51f4a-image.png" alt="ALPON Cloud Applications section">
      </a>
      <div class="sf-lightbox__caption">The container shell button in the Applications section of ALPON Cloud.</div>
    </div>
  </div>

</div>
`}</HTMLBlock>

> **Production image policy:** Replace floating `:latest` references with a reviewed immutable tag or digest, then record the selected version for rollback.

<HTMLBlock>{`
<div class="sf-doc">
  <section class="sf-related" aria-labelledby="related-guides-heading">
    <div class="sf-related__head">
      <div>
        <span class="sf-related__eyebrow">Continue exploring</span>
        <h2 class="sf-related__title" id="related-guides-heading">Related guides</h2>
      </div>
      <p class="sf-related__intro">Build on this workflow with deployment, fleet-management, and hardware guidance for the ALPON platform.</p>
    </div>
    <div class="sf-related__grid">
      <a class="sf-related__card" href="/docs/alpon-deploy-applications">
        <span class="sf-related__icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M5 21h14"/></svg></span>
        <span class="sf-related__kicker">Workflow</span>
        <strong class="sf-related__card-title">Deploy Applications on ALPON</strong>
        <span class="sf-related__desc">Push an ARM64 image, configure its runtime, and launch it from ALPON Cloud.</span>
        <span class="sf-related__action">Open guide <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg></span>
      </a>
      <a class="sf-related__card" href="/docs/alpon-cloud">
        <span class="sf-related__icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18"/><path d="M8 14h3"/></svg></span>
        <span class="sf-related__kicker">Platform</span>
        <strong class="sf-related__card-title">ALPON Cloud</strong>
        <span class="sf-related__desc">Manage devices, applications, connectivity, and remote operations from one workspace.</span>
        <span class="sf-related__action">Open guide <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg></span>
      </a>
      <a class="sf-related__card" href="/docs/alpon-device-comparison">
        <span class="sf-related__icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="3" width="14" height="18" rx="2"/><path d="M9 7h6M9 11h6M9 15h3"/></svg></span>
        <span class="sf-related__kicker">Hardware</span>
        <strong class="sf-related__card-title">Compare ALPON devices</strong>
        <span class="sf-related__desc">Compare ALPON X4 and ALPON X5 AI capabilities for your edge workload.</span>
        <span class="sf-related__action">Open guide <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg></span>
      </a>
    </div>
  </section>
</div>
`}</HTMLBlock>