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

# Containerize Apps for ALPON with Dockerfile Templates

Learn how to containerize applications on the ALPON Edge Computers using Docker and optimized Dockerfile templates. Build lightweight ARM64 containers for edge computing and IoT deployments.

<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 ══ */

/* ══ PAGE COMPONENTS (this page only; canonical block above is shared) ══ */
.sf-doc .sf-lead { font-size:1.125rem; line-height:1.6; color:var(--deep-ink); margin:0 0 20px; font-weight:400; }
.sf-doc .sf-method-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(320px, 1fr)); gap:16px; margin:16px 0 32px; }
.sf-doc .sf-card { background:var(--white); color:var(--deep-ink); border:1px solid var(--line); border-radius:var(--r-xl); padding:var(--lg); transition:border-color .15s, box-shadow .15s, transform .15s; }
.sf-doc .sf-card:hover { border-color:var(--slate-light); transform:translateY(-1px); }
.sf-doc .sf-method__head { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:10px; }
.sf-doc .sf-method__title { font-size:1rem; font-weight:700; color:var(--deep-ink); display:inline-flex; align-items:center; gap:8px; }
.sf-doc .sf-method__title svg { width:18px; height:18px; color:var(--primary); }
.sf-doc .sf-method-grid > .sf-card > p { font-size:0.9375rem; margin:0 0 12px; color:var(--deep-ink); }
.sf-doc .sf-quick-grid { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:14px; margin:16px 0 28px; }
.sf-doc .sf-quick-card { background:var(--white); border:1px solid var(--line); border-radius:var(--r-lg); padding:14px 16px; display:grid; grid-template-columns:auto 1fr; grid-template-areas:"icon title" "desc desc"; column-gap:10px; row-gap:8px; align-items:center; transition:border-color .15s, transform .15s; }
.sf-doc .sf-quick-card:hover { border-color:var(--primary); transform:translateY(-1px); }
.sf-doc .sf-quick-card__icon { grid-area:icon; background:transparent; color:var(--primary); display:inline-flex; align-items:center; justify-content:center; }
.sf-doc .sf-quick-card__icon svg { width:20px; height:20px; }
.sf-doc .sf-quick-card__title { grid-area:title; font-size:0.9375rem; font-weight:700; color:var(--deep-ink); line-height:1.2; }
.sf-doc .sf-quick-card__desc { grid-area:desc; font-size:0.8125rem; color:var(--slate); line-height:1.5; }
.sf-doc .sf-quick-card__desc strong { color:var(--deep-ink); }
.sf-doc .sf-quick-card__desc code { font-size:0.75rem; }
.sf-doc .sf-practice-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(340px, 1fr)); gap:14px; margin:18px 0 28px; }
.sf-doc .sf-practice-card { position:relative; background:var(--white); border:1px solid var(--line); border-radius:var(--r-xl); padding:22px 22px 20px; display:flex; flex-direction:column; gap:10px; transition:border-color .18s ease, transform .18s ease, box-shadow .18s ease; overflow:hidden; }
.sf-doc .sf-practice-card::before { content:''; position:absolute; top:0; left:0; right:0; height:2px; background:linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); opacity:0; transition:opacity .18s ease; }
.sf-doc .sf-practice-card:hover { border-color:var(--slate-light); transform:translateY(-2px); box-shadow:0 8px 20px rgba(10, 23, 33, 0.06); }
.sf-doc .sf-practice-card:hover::before { opacity:1; }
.sf-doc .sf-practice-card__head { display:flex; align-items:center; gap:12px; margin-bottom:2px; }
.sf-doc .sf-practice-card__icon { background:transparent; color:var(--primary); display:inline-flex; align-items:center; justify-content:center; flex-shrink:0; }
.sf-doc .sf-practice-card__icon svg { width:22px; height:22px; stroke:currentColor; stroke-width:1.85; fill:none; }
.sf-doc .sf-practice-card__index { font-family:var(--font-sans); font-size:0.6875rem; font-weight:700; letter-spacing:0.14em; text-transform:uppercase; color:var(--slate); }
.sf-doc .sf-practice-card__title { font-family:var(--font-sans); font-size:1.0625rem; font-weight:700; letter-spacing:-0.015em; color:var(--deep-ink); line-height:1.25; margin:0; }
.sf-doc .sf-practice-card__desc { font-size:0.9rem; line-height:1.55; color:var(--slate); margin:0; flex:1; }
.sf-doc .sf-practice-card__desc strong { color:var(--deep-ink); font-weight:600; }
.sf-doc .sf-practice-card__desc code { font-size:0.75rem; background:var(--light); color:var(--primary); padding:1px 5px; border-radius:var(--r-sm); border:1px solid var(--line); }
.sf-doc .sf-practice-card__example { font-family:var(--font-mono); font-size:0.75rem; line-height:1.65; background:#0d1117; color:#e6edf3; padding:10px 12px; border-radius:var(--r-md); border:1px solid #30363d; overflow-x:auto; margin-top:6px; white-space:pre; }
.sf-doc .sf-practice-card__example .ex-good { color:#56d364; }
.sf-doc .sf-practice-card__example .ex-good::before { content:'+ '; color:#56d364; font-weight:700; }
.sf-doc .sf-practice-card__example .ex-bad { color:#ff7b72; text-decoration:line-through; text-decoration-color:rgba(255, 123, 114, 0.55); }
.sf-doc .sf-practice-card__example .ex-bad::before { content:'- '; color:#ff7b72; font-weight:700; text-decoration:none; }
.sf-doc .sf-practice-card__example .ex-dim { color:#8B949E; }
.sf-doc .sf-next-step { position:relative; display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:22px; padding:26px 28px; background:var(--white); border:1px solid var(--line); border-radius:var(--r-2xl); margin:18px 0 0; text-decoration:none !important; overflow:hidden; transition:border-color .22s ease, transform .22s ease, box-shadow .22s ease; }
.sf-doc .sf-next-step::before { content:''; position:absolute; left:0; top:0; bottom:0; width:4px; background:linear-gradient(180deg, var(--primary) 0%, var(--secondary) 100%); }
.sf-doc .sf-next-step::after { content:''; position:absolute; inset:0; background:radial-gradient(ellipse at 100% 50%, rgba(0, 224, 184, 0.08), transparent 65%); opacity:0; transition:opacity .25s ease; pointer-events:none; }
.sf-doc .sf-next-step:hover { border-color:var(--primary); transform:translateY(-2px); box-shadow:0 14px 32px rgba(3, 72, 96, 0.12); opacity:1 !important; }
.sf-doc .sf-next-step:hover::after { opacity:1; }
.sf-doc .sf-next-step__visual { width:56px; height:56px; border-radius:var(--r-lg); background:linear-gradient(135deg, var(--primary) 0%, #0a6e8a 100%); color:var(--white); display:flex; align-items:center; justify-content:center; flex-shrink:0; position:relative; z-index:1; box-shadow:0 6px 14px rgba(3, 72, 96, 0.22); }
.sf-doc .sf-next-step__visual svg { width:26px; height:26px; stroke:currentColor; stroke-width:2; fill:none; }
.sf-doc .sf-next-step__body { display:flex; flex-direction:column; gap:4px; min-width:0; position:relative; z-index:1; }
.sf-doc .sf-next-step__label { font-family:var(--font-sans); font-size:0.6875rem; font-weight:700; letter-spacing:0.16em; text-transform:uppercase; color:var(--primary); }
.sf-doc .sf-next-step__title { font-family:var(--font-sans); font-size:1.25rem; font-weight:700; letter-spacing:-0.02em; color:var(--deep-ink); line-height:1.2; margin:0; }
.sf-doc .sf-next-step__desc { font-size:0.9375rem; line-height:1.5; color:var(--slate); margin:2px 0 0; }
.sf-doc .sf-next-step__desc code { font-size:0.8125rem; }
.sf-doc .sf-next-step__chips { display:flex; flex-wrap:wrap; gap:6px; margin-top:8px; }
.sf-doc .sf-next-step__chip { display:inline-flex; align-items:center; gap:5px; font-family:var(--font-sans); font-size:0.6875rem; font-weight:600; padding:3px 9px; border-radius:var(--r-sm); background:var(--light); color:var(--deep-ink); border:1px solid var(--line); line-height:1.4; }
.sf-doc .sf-next-step__chip svg { width:11px; height:11px; stroke:var(--primary); stroke-width:2.25; fill:none; }
.sf-doc .sf-next-step__action { display:inline-flex; align-items:center; justify-content:center; background:transparent; color:var(--primary); flex-shrink:0; position:relative; z-index:1; transition:transform .22s ease; }
.sf-doc .sf-next-step__action svg { width:24px; height:24px; stroke:currentColor; stroke-width:2.25; fill:none; }
.sf-doc .sf-next-step:hover .sf-next-step__action { transform:translateX(6px); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-next-step__visual { background:linear-gradient(135deg, var(--secondary) 0%, var(--alpon-cyan) 100%); color:var(--tertiary); box-shadow:0 6px 14px rgba(0,0,0,.45); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-next-step:hover { box-shadow:0 14px 32px rgba(0,0,0,.50); }
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-practice-card:hover { box-shadow:0 8px 20px rgba(0,0,0,.45); }
@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-next-step__visual { background:linear-gradient(135deg, var(--secondary) 0%, var(--alpon-cyan) 100%); color:var(--tertiary); box-shadow:0 6px 14px rgba(0,0,0,.45); }
  :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-next-step:hover { box-shadow:0 14px 32px rgba(0,0,0,.50); }
  :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-practice-card:hover { box-shadow:0 8px 20px rgba(0,0,0,.45); }
}
@media (max-width: 820px) {
  .sf-doc .sf-method-grid { grid-template-columns:1fr; }
  .sf-doc .sf-quick-grid { grid-template-columns:1fr; }
}
@media (max-width: 640px) {
  .sf-doc .sf-lead { font-size:1rem; }
  .sf-doc .sf-cta-card { flex-wrap:wrap; }
  .sf-doc .sf-practice-grid { grid-template-columns:1fr; }
  .sf-doc .sf-next-step { grid-template-columns:auto 1fr; grid-template-rows:auto auto; padding:22px; gap:14px 16px; }
  .sf-doc .sf-next-step__visual { width:48px; height:48px; }
  .sf-doc .sf-next-step__visual svg { width:22px; height:22px; }
  .sf-doc .sf-next-step__title { font-size:1.125rem; }
  .sf-doc .sf-next-step__action { grid-column:1 / -1; justify-self:start; margin-top:2px; }
}
/* Dark surfaces for the page components (the canonical block keeps --white
   at #FFFFFF in dark mode, so every card supplies its own dark surface). */
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-card,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-quick-card,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-practice-card,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-next-step { 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-card code,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-quick-card code,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-practice-card__desc code,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-next-step code { 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-quick-card__icon,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-practice-card__icon,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-method__title svg,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-next-step__action,
[data-color-mode="dark"]:where(.rm-ReadMe,:not(:has(.rm-ReadMe:is([data-color-mode="light"],[data-color-mode="system"])))) .sf-doc .sf-next-step__label { 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-card,
  :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-quick-card,
  :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-practice-card,
  :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-next-step { 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-card code,
  :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-quick-card code,
  :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-practice-card__desc code,
  :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-next-step code { 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-quick-card__icon,
  :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-practice-card__icon,
  :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-method__title 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-next-step__action,
  :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-next-step__label { color: var(--secondary); }
}
</style>

<div class="sf-doc">

  
  <header class="sf-hero">
    <div class="sf-hero__copy">
      <p class="sf-hero__title">Containerize Apps for ALPON with Dockerfile Templates</p>
      <p class="sf-hero__lead">
        Package your application as an ARM64 Docker image that runs on both <strong>ALPON X5 AI</strong> and
        <strong>ALPON X4</strong>. This guide walks through preparing your project, writing the Dockerfile,
        and using two ready-made templates (Debian for development, Alpine for production) so the resulting
        image can be deployed through ALPON Cloud.
      </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">Docker · ARM64</span>
        <span class="sf-pill">Dockerfile Templates</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>App Containerization</span>
  </div>


  
  <div class="sf-aeo">
    <span class="sf-aeo__label">How do I containerize an application for ALPON?</span>
    <p class="sf-aeo__text">
      Place your source, dependencies, and a <code>.dockerignore</code> in one project folder, then write a
      <strong>Dockerfile</strong> that picks an ARM64 base image, sets a working directory, copies your code,
      installs dependencies, and declares the startup command. Build for ARM64 with
      <code>docker buildx build --platform linux/arm64</code>, then push the image to the
      <strong>Sixfab Container Registry</strong> at <code>cr.sixfab.io</code>. From there, deploy it to any
      <strong>ALPON X5 AI</strong> or <strong>ALPON X4</strong> through ALPON Cloud.
    </p>
  </div>


  
  <div class="sf-callout sf-callout--info">
    <div class="sf-callout__header">
      <svg 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>Build for ARM64, not x86</span>
    </div>
    <div class="sf-callout__content">
      <p>Both ALPON X5 AI (Raspberry Pi CM5 with Cortex-A76) and ALPON X4 (Raspberry Pi CM4 with Cortex-A72) run on
      <strong>ARM64</strong>. Every image you deploy must include a <code>linux/arm64</code> variant. Most official
      images on Docker Hub (nginx, redis, postgres, node, python) already ship multi-arch manifests. Custom
      images built on an x86 workstation must use <code>docker buildx build --platform linux/arm64</code>. A
      plain <code>docker build</code> on a Mac or PC produces an x86 image that fails to start on ALPON with
      <code>exec format error</code>.</p>
    </div>
  </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="#why-alpon"><span class="sf-toc__num">01</span>Why ALPON</a>
    <a class="sf-toc__item" href="#traditional-vs-container"><span class="sf-toc__num">02</span>Traditional vs. containers</a>
    <a class="sf-toc__item" href="#prepare"><span class="sf-toc__num">03</span>Prepare the project</a>
    <a class="sf-toc__item" href="#dockerfile"><span class="sf-toc__num">04</span>Write the Dockerfile</a>
    <a class="sf-toc__item" href="#templates"><span class="sf-toc__num">05</span>Dockerfile templates</a>
    <a class="sf-toc__item" href="#best-practices"><span class="sf-toc__num">06</span>Best practices</a>
    <a class="sf-toc__item" href="#next-steps"><span class="sf-toc__num">07</span>Next steps</a>
  </nav>


  
  <h2 id="why-alpon">Why ALPON for containerization</h2>

  <p>Every ALPON device ships with a Docker runtime preinstalled and authenticated against the Sixfab Container
  Registry out of the box. There is no SSH setup, no per-device credential rotation, no manual
  <code>docker pull</code>. The two devices in the line share the same ARM64 build target, so a single image
  works on both.</p>

  <div class="sf-quick-grid">
    <div class="sf-quick-card">
      <span class="sf-quick-card__icon">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="5" width="14" height="14" rx="1.5"/><rect x="9" y="9" width="6" height="6"/><line x1="8" y1="2" x2="8" y2="5"/><line x1="12" y1="2" x2="12" y2="5"/><line x1="16" y1="2" x2="16" y2="5"/><line x1="8" y1="19" x2="8" y2="22"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="16" y1="19" x2="16" y2="22"/><line x1="19" y1="8" x2="22" y2="8"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="19" y1="16" x2="22" y2="16"/><line x1="2" y1="8" x2="5" y2="8"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="2" y1="16" x2="5" y2="16"/></svg>
      </span>
      <span class="sf-quick-card__title">ALPON X5 AI</span>
      <span class="sf-quick-card__desc">Raspberry Pi <strong>CM5</strong>, Cortex-A76, 4-core ARM64, 8 GB RAM. Adds the DEEPX DX-M1 NPU for AI workloads.</span>
    </div>
    <div class="sf-quick-card">
      <span class="sf-quick-card__icon">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="5" width="14" height="14" rx="1.5"/><rect x="9" y="9" width="6" height="6"/><line x1="8" y1="2" x2="8" y2="5"/><line x1="12" y1="2" x2="12" y2="5"/><line x1="16" y1="2" x2="16" y2="5"/><line x1="8" y1="19" x2="8" y2="22"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="16" y1="19" x2="16" y2="22"/><line x1="19" y1="8" x2="22" y2="8"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="19" y1="16" x2="22" y2="16"/><line x1="2" y1="8" x2="5" y2="8"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="2" y1="16" x2="5" y2="16"/></svg>
      </span>
      <span class="sf-quick-card__title">ALPON X4</span>
      <span class="sf-quick-card__desc">Raspberry Pi <strong>CM4</strong>, Cortex-A72, 4-core ARM64, 8 GB RAM. General-purpose IoT and edge compute.</span>
    </div>
    <div class="sf-quick-card">
      <span class="sf-quick-card__icon">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>
      </span>
      <span class="sf-quick-card__title">Docker preinstalled</span>
      <span class="sf-quick-card__desc">Runtime ready on first boot. No <code>apt install docker</code>, no daemon config.</span>
    </div>
    <div class="sf-quick-card">
      <span class="sf-quick-card__icon">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
      </span>
      <span class="sf-quick-card__title">One image, two devices</span>
      <span class="sf-quick-card__desc">A single <code>linux/arm64</code> image runs on both ALPON X5 AI and ALPON X4 without changes.</span>
    </div>
  </div>


  
  <h2 id="traditional-vs-container">Traditional vs. containerized application management</h2>

  <p>In a traditional setup each application sits on its own OS or virtual machine, brings its own dependencies,
  and is updated through manual <code>scp</code> and <code>systemctl</code> rituals. Scaling means provisioning
  more hosts. Debugging means dialling into individual devices. On a fleet of edge devices that pattern collapses
  fast.</p>

  <p>Containers flip the model. The application and every dependency it needs are baked into one immutable image.
  The same image runs identically on every device, deployments take seconds, and rollbacks are a tag change
  rather than a hot-fix shell session. For ALPON fleets, that is the only model we recommend.</p>

  <div class="sf-spec-grid">

    <div class="sf-spec-card">
      <div class="sf-spec-card__head">
        <span class="sf-spec-card__icon">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
        </span>
        <span class="sf-spec-card__title">Traditional setup</span>
      </div>
      <div class="sf-spec-card__body">
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Dependencies</span>
          <span class="sf-spec-row__val">Installed per device, drift over time</span>
        </div>
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Deployment</span>
          <span class="sf-spec-row__val">Manual file transfer, custom restart scripts</span>
        </div>
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Rollback</span>
          <span class="sf-spec-row__val">Reinstall older binaries, debug breakage live</span>
        </div>
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Isolation</span>
          <span class="sf-spec-row__val">Shared user-space, dependency conflicts</span>
        </div>
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Fleet update</span>
          <span class="sf-spec-row__val">SSH loop across devices</span>
        </div>
      </div>
    </div>

    <div class="sf-spec-card">
      <div class="sf-spec-card__head">
        <span class="sf-spec-card__icon">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
        </span>
        <span class="sf-spec-card__title">Containerized on ALPON</span>
      </div>
      <div class="sf-spec-card__body">
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Dependencies</span>
          <span class="sf-spec-row__val">Frozen in the image, identical on every device</span>
        </div>
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Deployment</span>
          <span class="sf-spec-row__val">Configured from ALPON Cloud; start time depends on image size and network conditions</span>
        </div>
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Rollback</span>
          <span class="sf-spec-row__val">Redeploy a previously tested immutable image tag</span>
        </div>
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Isolation</span>
          <span class="sf-spec-row__val">Each container runs in its own namespace</span>
        </div>
        <div class="sf-spec-row">
          <span class="sf-spec-row__key">Fleet update</span>
          <span class="sf-spec-row__val">Push a new tag, deploy from Connect</span>
        </div>
      </div>
    </div>

  </div>


  <hr>

  
  <h2 id="prepare">Prepare the project for containerization</h2>

  <p>Before writing the Dockerfile, get the project folder in shape. The image build process copies whatever
  is in this directory into the container, so anything that belongs out should be excluded explicitly.</p>

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

    
    <li class="sf-step" id="step-deps">
      <div class="sf-step__header">
        <div class="sf-step__num">1</div>
        <h3 class="sf-step__title">List every dependency</h3>
      </div>
      <div class="sf-step__body">
        <p>Every library, runtime, and system tool the app needs must be declared somewhere the build can read.
        Use the convention for your language:</p>
        <ul>
          <li><strong>Python:</strong> <code>requirements.txt</code> (or <code>pyproject.toml</code> for Poetry or uv).</li>
          <li><strong>Node.js:</strong> <code>package.json</code> with <code>package-lock.json</code>.</li>
          <li><strong>Go:</strong> <code>go.mod</code> with <code>go.sum</code>.</li>
          <li><strong>System packages:</strong> list them in the Dockerfile <code>RUN apt-get install</code> (Debian) or <code>RUN apk add</code> (Alpine) line.</li>
        </ul>
        <p>A reference Python project layout:</p>
        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
            <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
            <span class="sf-terminal__dot" style="background:#27C93F;"></span>
            <span class="sf-terminal__title">project layout</span>
            <span class="sf-terminal__lang">tree</span>
          </div>
          <div class="sf-terminal__body">
<pre><span class="t-green">my_app/</span>
<span class="t-dim">├──</span> app.py                 <span class="t-dim"># Main application entrypoint</span>
<span class="t-dim">├──</span> requirements.txt       <span class="t-dim"># Python dependencies</span>
<span class="t-dim">├──</span> Dockerfile
<span class="t-dim">└──</span> .dockerignore          <span class="t-dim"># Files excluded from the image</span></pre>
          </div>
        </div>
      </div>
    </li>

    
    <li class="sf-step" id="step-ignore">
      <div class="sf-step__header">
        <div class="sf-step__num">2</div>
        <h3 class="sf-step__title">Add a <code>.dockerignore</code> file</h3>
      </div>
      <div class="sf-step__body">
        <p>Without a <code>.dockerignore</code>, every log file, secret, and IDE folder gets copied into the image.
        That bloats the size, slows pulls over cellular, and can leak credentials. Treat it like a
        <code>.gitignore</code> for the build context:</p>
        <div class="sf-terminal">
          <div class="sf-terminal__header">
            <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
            <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
            <span class="sf-terminal__dot" style="background:#27C93F;"></span>
            <span class="sf-terminal__title">.dockerignore</span>
            <span class="sf-terminal__lang">ignore</span>
          </div>
          <div class="sf-terminal__body">
<pre><span class="t-dim"># Logs and runtime artefacts</span>
<span class="t-yel">*.log</span>
<span class="t-yel">*.pyc</span>
<span class="t-yel">__pycache__/</span>

<span class="t-dim"># Tests and dev tooling</span>
<span class="t-yel">tests/</span>
<span class="t-yel">.vscode/</span>
<span class="t-yel">.idea/</span>

<span class="t-dim"># Secrets and local config</span>
<span class="t-yel">.env</span>
<span class="t-yel">.env.*</span>

<span class="t-dim"># Version control</span>
<span class="t-yel">.git/</span>
<span class="t-yel">.gitignore</span></pre>
          </div>
        </div>
      </div>
    </li>

    
    <li class="sf-step" id="step-surface">
      <div class="sf-step__header">
        <div class="sf-step__num">3</div>
        <h3 class="sf-step__title">Identify the runtime surface</h3>
      </div>
      <div class="sf-step__body">
        <p>Decide upfront what the container needs from the host. These three answers shape the deployment form
        in ALPON Cloud later:</p>
        <ul>
          <li><strong>Environment variables:</strong> anything that changes between dev and prod (database URLs,
          API keys, feature flags). Never hard-code these in the source.</li>
          <li><strong>Ports:</strong> which TCP/UDP port the app listens on inside the container. Choose an available host port in the deployment form. The container-side port is defined by the application.</li>
          <li><strong>Bind mounts:</strong> host paths the container needs to read or write (persistent data,
          device files, or configuration). The source path must already exist on the device; the deployment
          form does not create named Docker volumes.</li>
        </ul>
      </div>
    </li>

  </ol>

  <hr>

  
  <h2 id="dockerfile">Write the Dockerfile</h2>

  <p>The Dockerfile is the recipe for the image. Each instruction creates a new image layer; ordered correctly,
  it lets Docker cache the slow steps and rebuild only what changed. The five instructions below cover the
  vast majority of edge applications.</p>

  <div class="sf-spec-card">
    <div class="sf-spec-card__head">
      <span class="sf-spec-card__icon">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true"><path d="M16 18 22 12 16 6"/><path d="M8 6 2 12 8 18"/></svg>
      </span>
      <span class="sf-spec-card__title">Core Dockerfile instructions</span>
    </div>
    <div class="sf-spec-card__body">
      <div class="sf-spec-row">
        <span class="sf-spec-row__key"><code>FROM</code></span>
        <span class="sf-spec-row__val">Picks the base image. Always pin a tag (e.g. <code>python:3.11-slim</code>), never use <code>latest</code> in production.</span>
      </div>
      <div class="sf-spec-row">
        <span class="sf-spec-row__key"><code>WORKDIR</code></span>
        <span class="sf-spec-row__val">Sets the working directory inside the container. Subsequent instructions run here.</span>
      </div>
      <div class="sf-spec-row">
        <span class="sf-spec-row__key"><code>COPY</code></span>
        <span class="sf-spec-row__val">Copies files from the build context into the image. Copy <code>requirements.txt</code> before the rest of the source to maximise cache reuse.</span>
      </div>
      <div class="sf-spec-row">
        <span class="sf-spec-row__key"><code>RUN</code></span>
        <span class="sf-spec-row__val">Executes a shell command at build time. Use it to install system packages and language dependencies.</span>
      </div>
      <div class="sf-spec-row">
        <span class="sf-spec-row__key"><code>CMD</code></span>
        <span class="sf-spec-row__val">Defines the command that runs when the container starts. Always exec-form: <code>CMD ["python", "app.py"]</code>.</span>
      </div>
    </div>
  </div>

  <h3>A minimal Python Dockerfile, walked through</h3>

  <p>Pick the base image. The <code>-slim</code> variants are far smaller than the default and still include
  enough to install most pure-Python packages:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">Dockerfile</span>
      <span class="sf-terminal__lang">dockerfile</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-dim"># Pinned, slim, ARM64-compatible Python runtime</span>
<span class="t-pp">FROM</span> python:3.11-slim</pre>
    </div>
  </div>

  <p>Set the working directory. Every <code>COPY</code> and <code>RUN</code> that follows resolves relative to
  it:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">Dockerfile</span>
      <span class="sf-terminal__lang">dockerfile</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-pp">WORKDIR</span> /app</pre>
    </div>
  </div>

  <p>Copy <code>requirements.txt</code> first, install, then copy the rest of the source. This ordering means
  Docker only rebuilds the dependency layer when <code>requirements.txt</code> changes, so code-only edits skip
  the slow <code>pip install</code> step entirely:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">Dockerfile</span>
      <span class="sf-terminal__lang">dockerfile</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-pp">COPY</span> requirements.txt .
<span class="t-pp">RUN</span> pip install <span class="t-blue">--no-cache-dir</span> <span class="t-blue">-r</span> requirements.txt

<span class="t-pp">COPY</span> . .</pre>
    </div>
  </div>

  <p>Declare the startup command. Use exec-form (the JSON array, not the shell string) so signals like
  <code>SIGTERM</code> reach the app cleanly when the container stops:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">Dockerfile</span>
      <span class="sf-terminal__lang">dockerfile</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-pp">CMD</span> [<span class="t-str">"python"</span>, <span class="t-str">"app.py"</span>]</pre>
    </div>
  </div>

  <h3>The complete Dockerfile</h3>

  <p>Combine the pieces above:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">Dockerfile</span>
      <span class="sf-terminal__lang">dockerfile</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-dim"># Pinned, slim, ARM64-compatible Python runtime</span>
<span class="t-pp">FROM</span> python:3.11-slim

<span class="t-dim"># Working directory for the rest of the build</span>
<span class="t-pp">WORKDIR</span> /app

<span class="t-dim"># Install dependencies first for better cache reuse</span>
<span class="t-pp">COPY</span> requirements.txt .
<span class="t-pp">RUN</span> pip install <span class="t-blue">--no-cache-dir</span> <span class="t-blue">-r</span> requirements.txt

<span class="t-dim"># Copy the rest of the source</span>
<span class="t-pp">COPY</span> . .

<span class="t-dim"># Start the application</span>
<span class="t-pp">CMD</span> [<span class="t-str">"python"</span>, <span class="t-str">"app.py"</span>]</pre>
    </div>
  </div>

  <h3>Build it for ARM64</h3>

  <p>From the project root on your workstation:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">terminal</span>
      <span class="sf-terminal__lang">bash</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-dim"># 1. Enable buildx (one-time, if not already)</span>
<span class="t-green">docker</span> buildx create <span class="t-blue">--use</span>

<span class="t-dim"># 2. Build the ARM64 image and load it locally</span>
<span class="t-green">docker</span> buildx build <span class="t-blue">--load</span> <span class="t-blue">--platform</span> linux/arm64 <span class="t-blue">-t</span> my-app:latest .</pre>
    </div>
  </div>

  <hr>

  
  <h2 id="templates">Dockerfile templates</h2>

  <p>Two ready-made templates cover the two phases of work. Use the Debian template while iterating; switch to
  the Alpine template before shipping to a fleet.</p>

  <div class="sf-method-grid">

    
    <div class="sf-card">
      <div class="sf-method__head">
        <span class="sf-method__title">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m18 16 4-4-4-4M6 8l-4 4 4 4M14.5 4l-5 16"/></svg>
          Development · Debian
        </span>
        <span class="sf-pill sf-pill--info">For iteration</span>
      </div>
      <p>Built on <code>debian:bookworm-slim</code>. Includes the build toolchain and git so you can compile
      native modules, debug from inside the container, and tweak dependencies without rebuilding the base.</p>
      <ul>
        <li>Includes <code>build-essential</code> and <code>git</code></li>
        <li>Familiar <code>apt</code> ecosystem</li>
        <li>Larger image (150 to 300 MB), fine for dev</li>
      </ul>
    </div>

    
    <div class="sf-card">
      <div class="sf-method__head">
        <span class="sf-method__title">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m2 17 10 5 10-5M2 12l10 5 10-5M12 2 2 7l10 5 10-5-10-5z"/></svg>
          Production · Alpine
        </span>
        <span class="sf-pill sf-pill--success">For deployment</span>
      </div>
      <p>Built on a versioned Alpine base. Stripped down to the minimum runtime, with no build tools or
      utilities the application does not need. Smaller images reduce cellular transfer and patching surface.</p>
      <ul>
        <li>Uses the <code>apk</code> package manager</li>
        <li>Image size depends on the application and runtime dependencies</li>
        <li>Fewer packages to transfer and maintain</li>
      </ul>
    </div>

  </div>

  <h3>Development Dockerfile (Debian-based)</h3>

  <p>Drop this in the project root during active development:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">Dockerfile.dev</span>
      <span class="sf-terminal__lang">dockerfile</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-dim"># Development Dockerfile, Debian-based</span>
<span class="t-pp">FROM</span> debian:bookworm-slim

<span class="t-pp">WORKDIR</span> /app

<span class="t-dim"># Build toolchain and dev utilities</span>
<span class="t-pp">RUN</span> apt-get update <span class="t-yel">&amp;&amp;</span> apt-get install <span class="t-blue">-y</span> <span class="t-yel">\</span>
    build-essential <span class="t-yel">\</span>
    git <span class="t-yel">\</span>
    <span class="t-yel">&amp;&amp;</span> rm -rf /var/lib/apt/lists/*

<span class="t-pp">COPY</span> . .

<span class="t-dim"># Mark the runtime environment</span>
<span class="t-pp">ENV</span> APP_ENV=development

<span class="t-dim"># Replace with your dev entrypoint</span>
<span class="t-pp">CMD</span> [<span class="t-str">"./your-dev-app"</span>]</pre>
    </div>
  </div>

  <h3>Production Dockerfile (Alpine-based)</h3>

  <p>Switch to this template once the app is stable. It optimises for image size, pull time, and the smaller
  attack surface that matters on a fleet of edge devices:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">Dockerfile</span>
      <span class="sf-terminal__lang">dockerfile</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-dim"># Production Dockerfile, Alpine-based</span>
<span class="t-pp">FROM</span> alpine:3.21

<span class="t-pp">WORKDIR</span> /app

<span class="t-dim"># Runtime-only dependencies, no build tools</span>
<span class="t-pp">RUN</span> apk add <span class="t-blue">--no-cache</span> <span class="t-yel">\</span>
    libc6-compat <span class="t-yel">\</span>
    <span class="t-yel">&amp;&amp;</span> rm -rf /var/cache/apk/*

<span class="t-pp">COPY</span> . .

<span class="t-dim"># Mark the runtime environment</span>
<span class="t-pp">ENV</span> APP_ENV=production

<span class="t-dim"># Replace with your prod entrypoint</span>
<span class="t-pp">CMD</span> [<span class="t-str">"./your-prod-app"</span>]</pre>
    </div>
  </div>

  <div class="sf-callout sf-callout--warning">
    <div class="sf-callout__header">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
      <span>Alpine and Debian use different package managers</span>
    </div>
    <div class="sf-callout__content">
      <p>Alpine uses <code>apk</code>; Debian uses <code>apt</code>. Package names also differ. Debian's
      <code>libssl-dev</code> is Alpine's <code>openssl-dev</code>; Debian's <code>libpq-dev</code> is Alpine's
      <code>postgresql-dev</code>. When porting from the dev template to prod, look up each <code>apt</code>
      package on <a href="https://pkgs.alpinelinux.org/packages" target="_blank" rel="noopener">pkgs.alpinelinux.org</a>
      and replace it with the Alpine equivalent. Native C extensions built against glibc may also need
      <code>libc6-compat</code> on Alpine (musl).</p>
    </div>
  </div>

  <hr>

  
  <h2 id="best-practices">Best practices for ALPON containers</h2>

  <p>The same image runs on both ALPON X5 AI and ALPON X4, but small choices in the Dockerfile decide whether
  it pulls in 10 seconds or 10 minutes over a cellular link, and whether it survives the next dependency
  update.</p>

  <div class="sf-practice-grid">

    
    <div class="sf-practice-card">
      <div class="sf-practice-card__head">
        <span class="sf-practice-card__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24"><path d="M20.5 7.5 12 3 3.5 7.5 12 12l8.5-4.5z"/><path d="M3.5 7.5v9L12 21l8.5-4.5v-9"/><path d="M12 12v9"/></svg>
        </span>
        <span class="sf-practice-card__index">01 · Image size</span>
      </div>
      <h4 class="sf-practice-card__title">Pick small base images</h4>
      <p class="sf-practice-card__desc">Prefer <code>-slim</code>, <code>-alpine</code>, or distroless variants when they satisfy the application's runtime requirements. Measure the final manifest size; transfer time depends on image size and the deployment network.</p>
      <div class="sf-practice-card__example"><span class="ex-good">FROM python:3.12-slim</span>
<span class="ex-bad">FROM python:3.12</span></div>
    </div>

    
    <div class="sf-practice-card">
      <div class="sf-practice-card__head">
        <span class="sf-practice-card__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24"><path d="M12 2v8"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 6-4 4-4-4"/><path d="M16 18a4 4 0 0 0-8 0"/></svg>
        </span>
        <span class="sf-practice-card__index">02 · Stability</span>
      </div>
      <h4 class="sf-practice-card__title">Pin every version</h4>
      <p class="sf-practice-card__desc">Avoid floating <code>latest</code> tags. Use a reviewed version tag and pin the digest where byte-for-byte reproducibility is required; update it deliberately after testing.</p>
      <div class="sf-practice-card__example"><span class="ex-good">FROM nginx:1.27-alpine@sha256:&lt;digest&gt;</span>
<span class="ex-bad">FROM nginx:latest</span></div>
    </div>

    
    <div class="sf-practice-card">
      <div class="sf-practice-card__head">
        <span class="sf-practice-card__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24"><path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z"/><path d="m9.5 14.5 5-5"/><path d="m9.5 9.5 5 5"/></svg>
        </span>
        <span class="sf-practice-card__index">03 · Build context</span>
      </div>
      <h4 class="sf-practice-card__title">Always include a <code>.dockerignore</code></h4>
      <p class="sf-practice-card__desc">Strip out <code>.git</code>, logs, secrets, IDE folders, and test artefacts. A clean build context is faster to send to the daemon and produces smaller, safer images.</p>
    </div>

    
    <div class="sf-practice-card">
      <div class="sf-practice-card__head">
        <span class="sf-practice-card__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v6c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/><path d="M3 11v6c0 1.66 4.03 3 9 3s9-1.34 9-3v-6"/></svg>
        </span>
        <span class="sf-practice-card__index">04 · Layer cache</span>
      </div>
      <h4 class="sf-practice-card__title">Order layers for cache reuse</h4>
      <p class="sf-practice-card__desc">Copy dependency manifests (<code>requirements.txt</code>, <code>package.json</code>) and install them <em>before</em> copying the rest of the source. Edits to <code>app.py</code> won't invalidate the dependency layer.</p>
      <div class="sf-practice-card__example"><span class="ex-good">COPY requirements.txt .</span>
<span class="ex-good">RUN pip install -r requirements.txt</span>
<span class="ex-good">COPY . .</span></div>
    </div>

    
    <div class="sf-practice-card">
      <div class="sf-practice-card__head">
        <span class="sf-practice-card__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
        </span>
        <span class="sf-practice-card__index">05 · Layer count</span>
      </div>
      <h4 class="sf-practice-card__title">Combine <code>RUN</code> commands</h4>
      <p class="sf-practice-card__desc">Each <code>RUN</code> creates a layer. Chain related commands with <code>&amp;&amp;</code> and clean up in the same line so removed files don't survive in the previous layer.</p>
      <div class="sf-practice-card__example"><span class="ex-good">RUN apt-get update &amp;&amp; \</span>
<span class="ex-good">    apt-get install -y curl &amp;&amp; \</span>
<span class="ex-good">    rm -rf /var/lib/apt/lists/*</span></div>
    </div>

    
    <div class="sf-practice-card">
      <div class="sf-practice-card__head">
        <span class="sf-practice-card__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
        </span>
        <span class="sf-practice-card__index">06 · Configuration</span>
      </div>
      <h4 class="sf-practice-card__title">Use <code>ENV</code> for configuration, not source code</h4>
      <p class="sf-practice-card__desc">Database URLs, API keys, log levels. Set them as environment variables on the deploy form. Hard-coded values mean a code change for every environment.</p>
    </div>

    
    <div class="sf-practice-card">
      <div class="sf-practice-card__head">
        <span class="sf-practice-card__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="2" x2="9" y2="4"/><line x1="15" y1="2" x2="15" y2="4"/><line x1="9" y1="20" x2="9" y2="22"/><line x1="15" y1="20" x2="15" y2="22"/><line x1="20" y1="9" x2="22" y2="9"/><line x1="20" y1="14" x2="22" y2="14"/><line x1="2" y1="9" x2="4" y2="9"/><line x1="2" y1="14" x2="4" y2="14"/></svg>
        </span>
        <span class="sf-practice-card__index">07 · Architecture</span>
      </div>
      <h4 class="sf-practice-card__title">Build with <code>buildx</code> for ARM64</h4>
      <p class="sf-practice-card__desc">A plain <code>docker build</code> on an x86 Mac or PC produces an x86 image that fails to start on ALPON. Always include <code>--platform linux/arm64</code>.</p>
      <div class="sf-practice-card__example"><span class="ex-good">docker buildx build --platform linux/arm64 .</span>
<span class="ex-bad">docker build .</span></div>
    </div>

    
    <div class="sf-practice-card">
      <div class="sf-practice-card__head">
        <span class="sf-practice-card__icon" aria-hidden="true">
          <svg viewBox="0 0 24 24"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
        </span>
        <span class="sf-practice-card__index">08 · Signal handling</span>
      </div>
      <h4 class="sf-practice-card__title">Use exec-form <code>CMD</code></h4>
      <p class="sf-practice-card__desc">Exec form passes signals (<code>SIGTERM</code> on stop) directly to your process, so the container shuts down cleanly instead of being killed nine seconds later.</p>
      <div class="sf-practice-card__example"><span class="ex-good">CMD ["python", "app.py"]</span>
<span class="ex-bad">CMD python app.py</span></div>
    </div>

  </div>

  <h3>A polished, production-ready example</h3>

  <p>Applying the practices above to a real Dockerfile:</p>

  <div class="sf-terminal">
    <div class="sf-terminal__header">
      <span class="sf-terminal__dot" style="background:#FF5F56;"></span>
      <span class="sf-terminal__dot" style="background:#FFBD2E;"></span>
      <span class="sf-terminal__dot" style="background:#27C93F;"></span>
      <span class="sf-terminal__title">Dockerfile</span>
      <span class="sf-terminal__lang">dockerfile</span>
    </div>
    <div class="sf-terminal__body">
<pre><span class="t-dim"># Pinned, slim, ARM64 base</span>
<span class="t-pp">FROM</span> python:3.11-slim

<span class="t-pp">WORKDIR</span> /app

<span class="t-dim"># System packages and Python deps in one layer, with cleanup</span>
<span class="t-pp">RUN</span> apt-get update <span class="t-yel">&amp;&amp;</span> apt-get install <span class="t-blue">-y</span> <span class="t-blue">--no-install-recommends</span> <span class="t-yel">\</span>
    libpq5 <span class="t-yel">\</span>
    <span class="t-yel">&amp;&amp;</span> rm -rf /var/lib/apt/lists/*

<span class="t-dim"># Dependencies first, cached unless requirements.txt changes</span>
<span class="t-pp">COPY</span> requirements.txt .
<span class="t-pp">RUN</span> pip install <span class="t-blue">--no-cache-dir</span> <span class="t-blue">-r</span> requirements.txt

<span class="t-dim"># Then the source</span>
<span class="t-pp">COPY</span> . .

<span class="t-dim"># Runtime configuration via ENV (override on the deploy form)</span>
<span class="t-pp">ENV</span> APP_ENV=production <span class="t-yel">\</span>
    LOG_LEVEL=info

<span class="t-dim"># Exec-form CMD for clean signal handling</span>
<span class="t-pp">CMD</span> [<span class="t-str">"python"</span>, <span class="t-str">"app.py"</span>]</pre>
    </div>
  </div>

  <hr>

  
  <h2 id="next-steps">Next steps: push and deploy</h2>

  <p>The image is built and tested locally. Two things remain: push it to the Sixfab Container Registry, then
  deploy it from ALPON Cloud. Both are covered end-to-end in the Deploy Applications guide, including the
  exact <code>docker login</code>, <code>docker tag</code>, and <code>docker push</code> sequence plus the
  deploy form fields for ports, environment variables, bind mounts, and network mode.</p>

  <a class="sf-next-step" href="/docs/alpon-deploy-applications" target="_blank" rel="noopener">
    <span class="sf-next-step__visual" aria-hidden="true">
      <svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
    </span>
    <span class="sf-next-step__body">
      <span class="sf-next-step__label">Continue with</span>
      <span class="sf-next-step__title">Deploy Applications on ALPON</span>
      <span class="sf-next-step__desc">Push the image to <code>cr.sixfab.io</code> and deploy it remotely from ALPON Cloud, with coverage of ports, environment variables, bind mounts, and network modes.</span>
      <span class="sf-next-step__chips">
        <span class="sf-next-step__chip">
          <svg viewBox="0 0 24 24" aria-hidden="true"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg>
          docker push
        </span>
        <span class="sf-next-step__chip">
          <svg viewBox="0 0 24 24" aria-hidden="true"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
          ALPON Cloud
        </span>
        <span class="sf-next-step__chip">
          <svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="m9 12 2 2 4-4"/></svg>
          Dashboard deployment
        </span>
      </span>
    </span>
    <span class="sf-next-step__action" aria-hidden="true">
      <svg viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
    </span>
  </a>

</div>




`}</HTMLBlock>

<br />

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