Files
docs/services/cloud-gpu/index.html
T

2856 lines
130 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Single source of truth for the homelab">
<link rel="canonical" href="http://192.168.1.8:13080/services/cloud-gpu/">
<link rel="prev" href="../adguard-dns/">
<link rel="next" href="../arcane/">
<link rel="icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.6">
<title>Cloud GPU - nuclide.systems docs</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="black" data-md-color-accent="blue">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#cloud-gpu-extension-scaleway-l40s" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="nuclide.systems docs" class="md-header__button md-logo" aria-label="nuclide.systems docs" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
nuclide.systems docs
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Cloud GPU
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="(prefers-color-scheme: dark)" data-md-color-scheme="slate" data-md-color-primary="black" data-md-color-accent="blue" aria-hidden="true" type="radio" name="__palette" id="__palette_0">
<input class="md-option" data-md-color-media="(prefers-color-scheme: light)" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="blue" aria-hidden="true" type="radio" name="__palette" id="__palette_1">
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../../CHANGELOG/" class="md-tabs__link">
Changelog
</a>
</li>
<li class="md-tabs__item">
<a href="../../RESUME/" class="md-tabs__link">
Resume
</a>
</li>
<li class="md-tabs__item">
<a href="../../ct-inventory/" class="md-tabs__link">
CT inventory
</a>
</li>
<li class="md-tabs__item">
<a href="../../infra/proxmox-state/" class="md-tabs__link">
Infra
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../homelab-architecture/" class="md-tabs__link">
Services
</a>
</li>
<li class="md-tabs__item">
<a href="../../security/data-leak-audit-comparison/" class="md-tabs__link">
Security & audits
</a>
</li>
<li class="md-tabs__item">
<a href="../../ideas/stack-ideas/" class="md-tabs__link">
Ideas
</a>
</li>
<li class="md-tabs__item">
<a href="../../history/traefik-migration/" class="md-tabs__link">
History
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="nuclide.systems docs" class="md-nav__button md-logo" aria-label="nuclide.systems docs" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
nuclide.systems docs
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../CHANGELOG/" class="md-nav__link">
<span class="md-ellipsis">
Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../RESUME/" class="md-nav__link">
<span class="md-ellipsis">
Resume
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../ct-inventory/" class="md-nav__link">
<span class="md-ellipsis">
CT inventory
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_5" >
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="0">
<span class="md-ellipsis">
Infra
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5">
<span class="md-nav__icon md-icon"></span>
Infra
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../infra/proxmox-state/" class="md-nav__link">
<span class="md-ellipsis">
Proxmox state
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../infra/portmap/" class="md-nav__link">
<span class="md-ellipsis">
Port map
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../infra/storage/" class="md-nav__link">
<span class="md-ellipsis">
Storage
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../infra/volumes/" class="md-nav__link">
<span class="md-ellipsis">
Volumes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../infra/docker-networks/" class="md-nav__link">
<span class="md-ellipsis">
Docker networks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../infra/connection-hosts/" class="md-nav__link">
<span class="md-ellipsis">
Connection hosts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../infra/proxmox-memory-audit/" class="md-nav__link">
<span class="md-ellipsis">
Memory audit
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_6" checked>
<label class="md-nav__link" for="__nav_6" id="__nav_6_label" tabindex="">
<span class="md-ellipsis">
Services
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_6_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_6">
<span class="md-nav__icon md-icon"></span>
Services
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../homelab-architecture/" class="md-nav__link">
<span class="md-ellipsis">
Homelab architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../dev-environment/" class="md-nav__link">
<span class="md-ellipsis">
Dev environment (Coder + Gitea)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../mcp-gateway/" class="md-nav__link">
<span class="md-ellipsis">
MCP gateway
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../mcp-servers/" class="md-nav__link">
<span class="md-ellipsis">
MCP servers
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../pocket-id/" class="md-nav__link">
<span class="md-ellipsis">
Pocket-ID (OIDC)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../backrest/" class="md-nav__link">
<span class="md-ellipsis">
Backrest (backups)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../databases/" class="md-nav__link">
<span class="md-ellipsis">
Databases
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../secrets-manager/" class="md-nav__link">
<span class="md-ellipsis">
Secrets manager
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../comfyui/" class="md-nav__link">
<span class="md-ellipsis">
ComfyUI
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../zoraxy/" class="md-nav__link">
<span class="md-ellipsis">
Zoraxy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../adguard-dns/" class="md-nav__link">
<span class="md-ellipsis">
AdGuard DNS
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Cloud GPU
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Cloud GPU
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#pricing-current-as-of-may-2026" class="md-nav__link">
<span class="md-ellipsis">
Pricing (current as of May 2026)
</span>
</a>
<nav class="md-nav" aria-label="Pricing (current as of May 2026)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#realistic-monthly-cost" class="md-nav__link">
<span class="md-ellipsis">
Realistic monthly cost
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#architecture" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#prerequisites" class="md-nav__link">
<span class="md-ellipsis">
Prerequisites
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-1-persistent-block-volume-models-live-here" class="md-nav__link">
<span class="md-ellipsis">
Step 1 — Persistent block volume (models live here)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-2-create-the-l40s-instance-with-cloud-init" class="md-nav__link">
<span class="md-ellipsis">
Step 2 — Create the L40S instance with cloud-init
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-3-wireguard-on-the-nuc" class="md-nav__link">
<span class="md-ellipsis">
Step 3 — WireGuard on the NUC
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-4-gpu-proxy-seamless-on-demand-startup" class="md-nav__link">
<span class="md-ellipsis">
Step 4 — gpu-proxy (seamless on-demand startup)
</span>
</a>
<nav class="md-nav" aria-label="Step 4 — gpu-proxy (seamless on-demand startup)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#aigpu-proxydocker-composeyml" class="md-nav__link">
<span class="md-ellipsis">
ai/gpu-proxy/docker-compose.yml
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#aigpu-proxyproxypy" class="md-nav__link">
<span class="md-ellipsis">
ai/gpu-proxy/proxy.py
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#aigpu-proxydockerfile" class="md-nav__link">
<span class="md-ellipsis">
ai/gpu-proxy/Dockerfile
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#step-5-wire-up-nuc-services" class="md-nav__link">
<span class="md-ellipsis">
Step 5 — Wire up NUC services
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-6-deploy" class="md-nav__link">
<span class="md-ellipsis">
Step 6 — Deploy
</span>
</a>
<nav class="md-nav" aria-label="Step 6 — Deploy">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#what-the-user-experience-looks-like" class="md-nav__link">
<span class="md-ellipsis">
What the user experience looks like
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#quick-start-stop-scripts" class="md-nav__link">
<span class="md-ellipsis">
Quick-start / stop scripts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scaleway-firewall-security-groups" class="md-nav__link">
<span class="md-ellipsis">
Scaleway firewall (security groups)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#model-storage-layout-on-the-200-gb-volume" class="md-nav__link">
<span class="md-ellipsis">
Model storage layout (on the 200 GB volume)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../arcane/" class="md-nav__link">
<span class="md-ellipsis">
Arcane
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../doc-ingestion/" class="md-nav__link">
<span class="md-ellipsis">
Doc ingestion pipeline
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../llm-benchmark/" class="md-nav__link">
<span class="md-ellipsis">
LLM benchmark
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_7" >
<label class="md-nav__link" for="__nav_7" id="__nav_7_label" tabindex="0">
<span class="md-ellipsis">
Security & audits
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_7_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_7">
<span class="md-nav__icon md-icon"></span>
Security & audits
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../security/data-leak-audit-comparison/" class="md-nav__link">
<span class="md-ellipsis">
Comparison
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../security/data-leak-audit-2026-05-20-tr004-cloud-sandbox/" class="md-nav__link">
<span class="md-ellipsis">
2026-05-20 · cloud-sandbox breach
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../security/data-leak-audit-2026-05-21-tr004-artifacts/" class="md-nav__link">
<span class="md-ellipsis">
2026-05-21 · artifacts (clean)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../security/audit-claude-code-meta/" class="md-nav__link">
<span class="md-ellipsis">
Self-audit (Claude Code)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../security/transcripts/audit-chat-2026-05-21/" class="md-nav__link">
<span class="md-ellipsis">
Transcript (audit session)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_8" >
<label class="md-nav__link" for="__nav_8" id="__nav_8_label" tabindex="0">
<span class="md-ellipsis">
Ideas
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_8_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_8">
<span class="md-nav__icon md-icon"></span>
Ideas
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../ideas/stack-ideas/" class="md-nav__link">
<span class="md-ellipsis">
Stack ideas
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9" >
<label class="md-nav__link" for="__nav_9" id="__nav_9_label" tabindex="0">
<span class="md-ellipsis">
History
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_9_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_9">
<span class="md-nav__icon md-icon"></span>
History
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../history/traefik-migration/" class="md-nav__link">
<span class="md-ellipsis">
Traefik (abandoned 2026-05-16)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../history/traefik-migration-docker-labels/" class="md-nav__link">
<span class="md-ellipsis">
Traefik labels (abandoned)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../history/mcp-gateway-requirements/" class="md-nav__link">
<span class="md-ellipsis">
MCP gateway requirements (superseded)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../history/scrubbing-list-2026-05-17/" class="md-nav__link">
<span class="md-ellipsis">
Scrubbing list (2026-05-17)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../history/case-study/" class="md-nav__link">
<span class="md-ellipsis">
Case study
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#pricing-current-as-of-may-2026" class="md-nav__link">
<span class="md-ellipsis">
Pricing (current as of May 2026)
</span>
</a>
<nav class="md-nav" aria-label="Pricing (current as of May 2026)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#realistic-monthly-cost" class="md-nav__link">
<span class="md-ellipsis">
Realistic monthly cost
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#architecture" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#prerequisites" class="md-nav__link">
<span class="md-ellipsis">
Prerequisites
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-1-persistent-block-volume-models-live-here" class="md-nav__link">
<span class="md-ellipsis">
Step 1 — Persistent block volume (models live here)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-2-create-the-l40s-instance-with-cloud-init" class="md-nav__link">
<span class="md-ellipsis">
Step 2 — Create the L40S instance with cloud-init
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-3-wireguard-on-the-nuc" class="md-nav__link">
<span class="md-ellipsis">
Step 3 — WireGuard on the NUC
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-4-gpu-proxy-seamless-on-demand-startup" class="md-nav__link">
<span class="md-ellipsis">
Step 4 — gpu-proxy (seamless on-demand startup)
</span>
</a>
<nav class="md-nav" aria-label="Step 4 — gpu-proxy (seamless on-demand startup)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#aigpu-proxydocker-composeyml" class="md-nav__link">
<span class="md-ellipsis">
ai/gpu-proxy/docker-compose.yml
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#aigpu-proxyproxypy" class="md-nav__link">
<span class="md-ellipsis">
ai/gpu-proxy/proxy.py
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#aigpu-proxydockerfile" class="md-nav__link">
<span class="md-ellipsis">
ai/gpu-proxy/Dockerfile
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#step-5-wire-up-nuc-services" class="md-nav__link">
<span class="md-ellipsis">
Step 5 — Wire up NUC services
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#step-6-deploy" class="md-nav__link">
<span class="md-ellipsis">
Step 6 — Deploy
</span>
</a>
<nav class="md-nav" aria-label="Step 6 — Deploy">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#what-the-user-experience-looks-like" class="md-nav__link">
<span class="md-ellipsis">
What the user experience looks like
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#quick-start-stop-scripts" class="md-nav__link">
<span class="md-ellipsis">
Quick-start / stop scripts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scaleway-firewall-security-groups" class="md-nav__link">
<span class="md-ellipsis">
Scaleway firewall (security groups)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#model-storage-layout-on-the-200-gb-volume" class="md-nav__link">
<span class="md-ellipsis">
Model storage layout (on the 200 GB volume)
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="cloud-gpu-extension-scaleway-l40s">Cloud GPU Extension — Scaleway L40S<a class="headerlink" href="#cloud-gpu-extension-scaleway-l40s" title="Permanent link">&para;</a></h1>
<p>Seamless on-demand GPU (FLUX.1-dev, video, multi-user) via a Scaleway L40S instance.
The instance auto-starts on first request and shuts down after 45 min idle.
Everything personal stays on the NUC.</p>
<hr />
<h2 id="pricing-current-as-of-may-2026">Pricing (current as of May 2026)<a class="headerlink" href="#pricing-current-as-of-may-2026" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>Resource</th>
<th>Rate</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>L40S-1-48G compute</td>
<td><strong>€1.40/hour</strong></td>
<td>PAR-2, billed per minute</td>
</tr>
<tr>
<td>Block volume 200 GB</td>
<td><strong>€16/month</strong></td>
<td>SSD, keeps models across restarts</td>
</tr>
<tr>
<td>Flexible IP</td>
<td>€0.004/hour</td>
<td>Static IP for WireGuard endpoint</td>
</tr>
<tr>
<td>Snapshots</td>
<td>€0.000044/GB/h</td>
<td>Only needed for image backups</td>
</tr>
</tbody>
</table>
<h3 id="realistic-monthly-cost">Realistic monthly cost<a class="headerlink" href="#realistic-monthly-cost" title="Permanent link">&para;</a></h3>
<table>
<thead>
<tr>
<th>Usage pattern</th>
<th>Compute</th>
<th>Storage</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Weekend sessions (8 h/week)</td>
<td>€45</td>
<td>€16</td>
<td><strong>~€61/month</strong></td>
</tr>
<tr>
<td>Daily 12 h</td>
<td>€63126</td>
<td>€16</td>
<td><strong>~€79142/month</strong></td>
</tr>
<tr>
<td>Heavy (4 h/day)</td>
<td>€168</td>
<td>€16</td>
<td><strong>~€184/month</strong></td>
</tr>
<tr>
<td>Always-on (don't)</td>
<td>€1,008</td>
<td>€16</td>
<td>€1,024/month</td>
</tr>
</tbody>
</table>
<p>The on-demand proxy below makes "daily 12 h" the natural default — you just click generate, it starts automatically.</p>
<hr />
<h2 id="architecture">Architecture<a class="headerlink" href="#architecture" title="Permanent link">&para;</a></h2>
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a>NUC (home, always-on) Scaleway PAR-2 (on demand)
<a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a>───────────────────────────── ───────────────────────────────
<a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>LobeChat ──▶ gpu-proxy:8190 ─── wg1 ──▶ ComfyUI :8188
<a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a>comfyui-mcp ──▶ (Docker) Ollama :11434 (optional)
<a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>
<a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a> ├─ start/stop via Scaleway API
<a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a> └─ idle watchdog (45 min → stop)
</code></pre></div>
<p><code>gpu-proxy</code> is a small Docker service that:
1. Forwards requests to the GPU instance
2. Auto-starts the Scaleway instance if it's stopped (cold-start ~90 s)
3. Shuts it down after 45 min with no traffic</p>
<hr />
<h2 id="prerequisites">Prerequisites<a class="headerlink" href="#prerequisites" title="Permanent link">&para;</a></h2>
<div class="highlight"><pre><span></span><code><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="c1"># Scaleway CLI</span>
<a id="__codelineno-1-2" name="__codelineno-1-2" href="#__codelineno-1-2"></a>curl<span class="w"> </span>-s<span class="w"> </span>https://raw.githubusercontent.com/scaleway/scaleway-cli/master/scripts/get.sh<span class="w"> </span><span class="p">|</span><span class="w"> </span>sh
<a id="__codelineno-1-3" name="__codelineno-1-3" href="#__codelineno-1-3"></a>scw<span class="w"> </span>init<span class="w"> </span><span class="c1"># enter API key + project ID</span>
</code></pre></div>
<hr />
<h2 id="step-1-persistent-block-volume-models-live-here">Step 1 — Persistent block volume (models live here)<a class="headerlink" href="#step-1-persistent-block-volume-models-live-here" title="Permanent link">&para;</a></h2>
<div class="highlight"><pre><span></span><code><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a><span class="c1"># Create 200 GB SSD volume in PAR-2</span>
<a id="__codelineno-2-2" name="__codelineno-2-2" href="#__codelineno-2-2"></a>scw<span class="w"> </span>block<span class="w"> </span>volume<span class="w"> </span>create<span class="w"> </span><span class="nv">name</span><span class="o">=</span>nuclide-gpu-models<span class="w"> </span><span class="nv">size</span><span class="o">=</span>200GB<span class="w"> </span><span class="nv">zone</span><span class="o">=</span>fr-par-2
<a id="__codelineno-2-3" name="__codelineno-2-3" href="#__codelineno-2-3"></a><span class="c1"># Note the volume ID: vol-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</span>
</code></pre></div>
<p>Models are kept on this volume. The compute instance can be deleted and recreated freely.</p>
<hr />
<h2 id="step-2-create-the-l40s-instance-with-cloud-init">Step 2 — Create the L40S instance with cloud-init<a class="headerlink" href="#step-2-create-the-l40s-instance-with-cloud-init" title="Permanent link">&para;</a></h2>
<p>Save as <code>/opt/stacks/scripts/gpu-cloud-init.yaml</code>:</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a><span class="c1">#cloud-config</span>
<a id="__codelineno-3-2" name="__codelineno-3-2" href="#__codelineno-3-2"></a><span class="nt">package_update</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
<a id="__codelineno-3-3" name="__codelineno-3-3" href="#__codelineno-3-3"></a><span class="nt">packages</span><span class="p">:</span>
<a id="__codelineno-3-4" name="__codelineno-3-4" href="#__codelineno-3-4"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">wireguard-tools</span>
<a id="__codelineno-3-5" name="__codelineno-3-5" href="#__codelineno-3-5"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker.io</span>
<a id="__codelineno-3-6" name="__codelineno-3-6" href="#__codelineno-3-6"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">docker-compose-v2</span>
<a id="__codelineno-3-7" name="__codelineno-3-7" href="#__codelineno-3-7"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nvidia-driver-545</span>
<a id="__codelineno-3-8" name="__codelineno-3-8" href="#__codelineno-3-8"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nvidia-container-toolkit</span>
<a id="__codelineno-3-9" name="__codelineno-3-9" href="#__codelineno-3-9"></a>
<a id="__codelineno-3-10" name="__codelineno-3-10" href="#__codelineno-3-10"></a><span class="nt">write_files</span><span class="p">:</span>
<a id="__codelineno-3-11" name="__codelineno-3-11" href="#__codelineno-3-11"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/etc/wireguard/wg0.conf</span>
<a id="__codelineno-3-12" name="__codelineno-3-12" href="#__codelineno-3-12"></a><span class="w"> </span><span class="nt">permissions</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;0600&#39;</span>
<a id="__codelineno-3-13" name="__codelineno-3-13" href="#__codelineno-3-13"></a><span class="w"> </span><span class="nt">content</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">|</span>
<a id="__codelineno-3-14" name="__codelineno-3-14" href="#__codelineno-3-14"></a><span class="w"> </span><span class="no">[Interface]</span>
<a id="__codelineno-3-15" name="__codelineno-3-15" href="#__codelineno-3-15"></a><span class="w"> </span><span class="no">Address = 10.200.0.1/24</span>
<a id="__codelineno-3-16" name="__codelineno-3-16" href="#__codelineno-3-16"></a><span class="w"> </span><span class="no">ListenPort = 51820</span>
<a id="__codelineno-3-17" name="__codelineno-3-17" href="#__codelineno-3-17"></a><span class="w"> </span><span class="no">PrivateKey = SCALEWAY_WG_PRIVKEY</span>
<a id="__codelineno-3-18" name="__codelineno-3-18" href="#__codelineno-3-18"></a>
<a id="__codelineno-3-19" name="__codelineno-3-19" href="#__codelineno-3-19"></a><span class="w"> </span><span class="no">[Peer]</span>
<a id="__codelineno-3-20" name="__codelineno-3-20" href="#__codelineno-3-20"></a><span class="w"> </span><span class="no"># NUC</span>
<a id="__codelineno-3-21" name="__codelineno-3-21" href="#__codelineno-3-21"></a><span class="w"> </span><span class="no">PublicKey = NUC_WG_PUBKEY</span>
<a id="__codelineno-3-22" name="__codelineno-3-22" href="#__codelineno-3-22"></a><span class="w"> </span><span class="no">AllowedIPs = 10.200.0.2/32</span>
<a id="__codelineno-3-23" name="__codelineno-3-23" href="#__codelineno-3-23"></a><span class="w"> </span><span class="no">PersistentKeepalive = 25</span>
<a id="__codelineno-3-24" name="__codelineno-3-24" href="#__codelineno-3-24"></a>
<a id="__codelineno-3-25" name="__codelineno-3-25" href="#__codelineno-3-25"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/opt/gpu/docker-compose.yml</span>
<a id="__codelineno-3-26" name="__codelineno-3-26" href="#__codelineno-3-26"></a><span class="w"> </span><span class="nt">content</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">|</span>
<a id="__codelineno-3-27" name="__codelineno-3-27" href="#__codelineno-3-27"></a><span class="w"> </span><span class="no">services:</span>
<a id="__codelineno-3-28" name="__codelineno-3-28" href="#__codelineno-3-28"></a><span class="w"> </span><span class="no">comfyui:</span>
<a id="__codelineno-3-29" name="__codelineno-3-29" href="#__codelineno-3-29"></a><span class="w"> </span><span class="no">image: yanwk/comfyui-boot:cu124</span>
<a id="__codelineno-3-30" name="__codelineno-3-30" href="#__codelineno-3-30"></a><span class="w"> </span><span class="no">container_name: comfyui</span>
<a id="__codelineno-3-31" name="__codelineno-3-31" href="#__codelineno-3-31"></a><span class="w"> </span><span class="no">ports:</span>
<a id="__codelineno-3-32" name="__codelineno-3-32" href="#__codelineno-3-32"></a><span class="w"> </span><span class="no">- &quot;10.200.0.1:8188:8188&quot;</span>
<a id="__codelineno-3-33" name="__codelineno-3-33" href="#__codelineno-3-33"></a><span class="w"> </span><span class="no">volumes:</span>
<a id="__codelineno-3-34" name="__codelineno-3-34" href="#__codelineno-3-34"></a><span class="w"> </span><span class="no">- /mnt/models:/root/ComfyUI/models</span>
<a id="__codelineno-3-35" name="__codelineno-3-35" href="#__codelineno-3-35"></a><span class="w"> </span><span class="no">- comfyui_config:/root/ComfyUI</span>
<a id="__codelineno-3-36" name="__codelineno-3-36" href="#__codelineno-3-36"></a><span class="w"> </span><span class="no">environment:</span>
<a id="__codelineno-3-37" name="__codelineno-3-37" href="#__codelineno-3-37"></a><span class="w"> </span><span class="no">- CLI_ARGS=--listen 0.0.0.0</span>
<a id="__codelineno-3-38" name="__codelineno-3-38" href="#__codelineno-3-38"></a><span class="w"> </span><span class="no">deploy:</span>
<a id="__codelineno-3-39" name="__codelineno-3-39" href="#__codelineno-3-39"></a><span class="w"> </span><span class="no">resources:</span>
<a id="__codelineno-3-40" name="__codelineno-3-40" href="#__codelineno-3-40"></a><span class="w"> </span><span class="no">reservations:</span>
<a id="__codelineno-3-41" name="__codelineno-3-41" href="#__codelineno-3-41"></a><span class="w"> </span><span class="no">devices:</span>
<a id="__codelineno-3-42" name="__codelineno-3-42" href="#__codelineno-3-42"></a><span class="w"> </span><span class="no">- driver: nvidia</span>
<a id="__codelineno-3-43" name="__codelineno-3-43" href="#__codelineno-3-43"></a><span class="w"> </span><span class="no">count: all</span>
<a id="__codelineno-3-44" name="__codelineno-3-44" href="#__codelineno-3-44"></a><span class="w"> </span><span class="no">capabilities: [gpu]</span>
<a id="__codelineno-3-45" name="__codelineno-3-45" href="#__codelineno-3-45"></a><span class="w"> </span><span class="no">restart: unless-stopped</span>
<a id="__codelineno-3-46" name="__codelineno-3-46" href="#__codelineno-3-46"></a>
<a id="__codelineno-3-47" name="__codelineno-3-47" href="#__codelineno-3-47"></a><span class="w"> </span><span class="no">ollama:</span>
<a id="__codelineno-3-48" name="__codelineno-3-48" href="#__codelineno-3-48"></a><span class="w"> </span><span class="no">image: ollama/ollama:latest</span>
<a id="__codelineno-3-49" name="__codelineno-3-49" href="#__codelineno-3-49"></a><span class="w"> </span><span class="no">container_name: ollama</span>
<a id="__codelineno-3-50" name="__codelineno-3-50" href="#__codelineno-3-50"></a><span class="w"> </span><span class="no">ports:</span>
<a id="__codelineno-3-51" name="__codelineno-3-51" href="#__codelineno-3-51"></a><span class="w"> </span><span class="no">- &quot;10.200.0.1:11434:11434&quot;</span>
<a id="__codelineno-3-52" name="__codelineno-3-52" href="#__codelineno-3-52"></a><span class="w"> </span><span class="no">volumes:</span>
<a id="__codelineno-3-53" name="__codelineno-3-53" href="#__codelineno-3-53"></a><span class="w"> </span><span class="no">- /mnt/models/ollama:/root/.ollama</span>
<a id="__codelineno-3-54" name="__codelineno-3-54" href="#__codelineno-3-54"></a><span class="w"> </span><span class="no">deploy:</span>
<a id="__codelineno-3-55" name="__codelineno-3-55" href="#__codelineno-3-55"></a><span class="w"> </span><span class="no">resources:</span>
<a id="__codelineno-3-56" name="__codelineno-3-56" href="#__codelineno-3-56"></a><span class="w"> </span><span class="no">reservations:</span>
<a id="__codelineno-3-57" name="__codelineno-3-57" href="#__codelineno-3-57"></a><span class="w"> </span><span class="no">devices:</span>
<a id="__codelineno-3-58" name="__codelineno-3-58" href="#__codelineno-3-58"></a><span class="w"> </span><span class="no">- driver: nvidia</span>
<a id="__codelineno-3-59" name="__codelineno-3-59" href="#__codelineno-3-59"></a><span class="w"> </span><span class="no">count: all</span>
<a id="__codelineno-3-60" name="__codelineno-3-60" href="#__codelineno-3-60"></a><span class="w"> </span><span class="no">capabilities: [gpu]</span>
<a id="__codelineno-3-61" name="__codelineno-3-61" href="#__codelineno-3-61"></a><span class="w"> </span><span class="no">restart: unless-stopped</span>
<a id="__codelineno-3-62" name="__codelineno-3-62" href="#__codelineno-3-62"></a>
<a id="__codelineno-3-63" name="__codelineno-3-63" href="#__codelineno-3-63"></a><span class="w"> </span><span class="no">volumes:</span>
<a id="__codelineno-3-64" name="__codelineno-3-64" href="#__codelineno-3-64"></a><span class="w"> </span><span class="no">comfyui_config:</span>
<a id="__codelineno-3-65" name="__codelineno-3-65" href="#__codelineno-3-65"></a>
<a id="__codelineno-3-66" name="__codelineno-3-66" href="#__codelineno-3-66"></a><span class="nt">runcmd</span><span class="p">:</span>
<a id="__codelineno-3-67" name="__codelineno-3-67" href="#__codelineno-3-67"></a><span class="w"> </span><span class="c1"># Mount the models volume (will be /dev/sdb or similar)</span>
<a id="__codelineno-3-68" name="__codelineno-3-68" href="#__codelineno-3-68"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mkdir -p /mnt/models</span>
<a id="__codelineno-3-69" name="__codelineno-3-69" href="#__codelineno-3-69"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="p p-Indicator">|</span>
<a id="__codelineno-3-70" name="__codelineno-3-70" href="#__codelineno-3-70"></a><span class="w"> </span><span class="no">DISK=$(lsblk -ndo NAME,SIZE | awk &#39;$2==&quot;200G&quot;{print &quot;/dev/&quot;$1}&#39; | head -1)</span>
<a id="__codelineno-3-71" name="__codelineno-3-71" href="#__codelineno-3-71"></a><span class="w"> </span><span class="no">if [ -n &quot;$DISK&quot; ]; then</span>
<a id="__codelineno-3-72" name="__codelineno-3-72" href="#__codelineno-3-72"></a><span class="w"> </span><span class="no">blkid &quot;$DISK&quot; || mkfs.ext4 &quot;$DISK&quot;</span>
<a id="__codelineno-3-73" name="__codelineno-3-73" href="#__codelineno-3-73"></a><span class="w"> </span><span class="no">echo &quot;$DISK /mnt/models ext4 defaults 0 2&quot; &gt;&gt; /etc/fstab</span>
<a id="__codelineno-3-74" name="__codelineno-3-74" href="#__codelineno-3-74"></a><span class="w"> </span><span class="no">mount &quot;$DISK&quot; /mnt/models</span>
<a id="__codelineno-3-75" name="__codelineno-3-75" href="#__codelineno-3-75"></a><span class="w"> </span><span class="no">fi</span>
<a id="__codelineno-3-76" name="__codelineno-3-76" href="#__codelineno-3-76"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mkdir -p /mnt/models/ollama</span>
<a id="__codelineno-3-77" name="__codelineno-3-77" href="#__codelineno-3-77"></a><span class="w"> </span><span class="c1"># Enable WireGuard</span>
<a id="__codelineno-3-78" name="__codelineno-3-78" href="#__codelineno-3-78"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">systemctl enable --now wg-quick@wg0</span>
<a id="__codelineno-3-79" name="__codelineno-3-79" href="#__codelineno-3-79"></a><span class="w"> </span><span class="c1"># Configure nvidia-container-toolkit</span>
<a id="__codelineno-3-80" name="__codelineno-3-80" href="#__codelineno-3-80"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nvidia-ctk runtime configure --runtime=docker</span>
<a id="__codelineno-3-81" name="__codelineno-3-81" href="#__codelineno-3-81"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">systemctl restart docker</span>
<a id="__codelineno-3-82" name="__codelineno-3-82" href="#__codelineno-3-82"></a><span class="w"> </span><span class="c1"># Start GPU services</span>
<a id="__codelineno-3-83" name="__codelineno-3-83" href="#__codelineno-3-83"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">cd /opt/gpu &amp;&amp; docker compose up -d</span>
</code></pre></div>
<p><strong>Before using this file</strong>, replace:
- <code>SCALEWAY_WG_PRIVKEY</code> → output of <code>wg genkey</code> (run on Scaleway instance side first)
- <code>NUC_WG_PUBKEY</code> → output of <code>cat /etc/wireguard/nuc_wg_pub</code> (see Step 3)</p>
<p>Launch the instance:</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="c1"># Generate WireGuard keys first (do this on the NUC)</span>
<a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a>wg<span class="w"> </span>genkey<span class="w"> </span><span class="p">|</span><span class="w"> </span>tee<span class="w"> </span>/etc/wireguard/scaleway_wg_priv<span class="w"> </span><span class="p">|</span><span class="w"> </span>wg<span class="w"> </span>pubkey<span class="w"> </span>&gt;<span class="w"> </span>/etc/wireguard/scaleway_wg_pub
<a id="__codelineno-4-3" name="__codelineno-4-3" href="#__codelineno-4-3"></a>wg<span class="w"> </span>genkey<span class="w"> </span><span class="p">|</span><span class="w"> </span>tee<span class="w"> </span>/etc/wireguard/nuc_wg_priv<span class="w"> </span><span class="p">|</span><span class="w"> </span>wg<span class="w"> </span>pubkey<span class="w"> </span>&gt;<span class="w"> </span>/etc/wireguard/nuc_wg_pub
<a id="__codelineno-4-4" name="__codelineno-4-4" href="#__codelineno-4-4"></a>
<a id="__codelineno-4-5" name="__codelineno-4-5" href="#__codelineno-4-5"></a><span class="c1"># Fill in cloud-init.yaml with the keys, then:</span>
<a id="__codelineno-4-6" name="__codelineno-4-6" href="#__codelineno-4-6"></a><span class="nv">INSTANCE_ID</span><span class="o">=</span><span class="k">$(</span>scw<span class="w"> </span>instance<span class="w"> </span>server<span class="w"> </span>create<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-7" name="__codelineno-4-7" href="#__codelineno-4-7"></a><span class="w"> </span><span class="nv">type</span><span class="o">=</span>L40S-1-48G<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-8" name="__codelineno-4-8" href="#__codelineno-4-8"></a><span class="w"> </span><span class="nv">image</span><span class="o">=</span>ubuntu_jammy_gpu<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-9" name="__codelineno-4-9" href="#__codelineno-4-9"></a><span class="w"> </span><span class="nv">zone</span><span class="o">=</span>fr-par-2<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-10" name="__codelineno-4-10" href="#__codelineno-4-10"></a><span class="w"> </span><span class="nv">name</span><span class="o">=</span>nuclide-gpu<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-11" name="__codelineno-4-11" href="#__codelineno-4-11"></a><span class="w"> </span>cloud-init<span class="o">=</span>@/opt/stacks/scripts/gpu-cloud-init.yaml<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-12" name="__codelineno-4-12" href="#__codelineno-4-12"></a><span class="w"> </span><span class="nv">output</span><span class="o">=</span>json<span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-r<span class="w"> </span><span class="s1">&#39;.id&#39;</span><span class="k">)</span>
<a id="__codelineno-4-13" name="__codelineno-4-13" href="#__codelineno-4-13"></a>
<a id="__codelineno-4-14" name="__codelineno-4-14" href="#__codelineno-4-14"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;Instance ID: </span><span class="nv">$INSTANCE_ID</span><span class="s2">&quot;</span>
<a id="__codelineno-4-15" name="__codelineno-4-15" href="#__codelineno-4-15"></a>
<a id="__codelineno-4-16" name="__codelineno-4-16" href="#__codelineno-4-16"></a><span class="c1"># Attach the models volume</span>
<a id="__codelineno-4-17" name="__codelineno-4-17" href="#__codelineno-4-17"></a>scw<span class="w"> </span>instance<span class="w"> </span>server<span class="w"> </span>attach-volume<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-18" name="__codelineno-4-18" href="#__codelineno-4-18"></a><span class="w"> </span>server-id<span class="o">=</span><span class="nv">$INSTANCE_ID</span><span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-19" name="__codelineno-4-19" href="#__codelineno-4-19"></a><span class="w"> </span>volume-id<span class="o">=</span>vol-xxxxxxxx<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-20" name="__codelineno-4-20" href="#__codelineno-4-20"></a><span class="w"> </span><span class="nv">zone</span><span class="o">=</span>fr-par-2
<a id="__codelineno-4-21" name="__codelineno-4-21" href="#__codelineno-4-21"></a>
<a id="__codelineno-4-22" name="__codelineno-4-22" href="#__codelineno-4-22"></a><span class="c1"># Allocate a Flexible IP (static IP that survives instance restarts)</span>
<a id="__codelineno-4-23" name="__codelineno-4-23" href="#__codelineno-4-23"></a><span class="nv">FLEXIP_ID</span><span class="o">=</span><span class="k">$(</span>scw<span class="w"> </span>instance<span class="w"> </span>ip<span class="w"> </span>create<span class="w"> </span><span class="nv">zone</span><span class="o">=</span>fr-par-2<span class="w"> </span><span class="nv">output</span><span class="o">=</span>json<span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-r<span class="w"> </span><span class="s1">&#39;.id&#39;</span><span class="k">)</span>
<a id="__codelineno-4-24" name="__codelineno-4-24" href="#__codelineno-4-24"></a>scw<span class="w"> </span>instance<span class="w"> </span>server<span class="w"> </span>attach-flexible-ip<span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-25" name="__codelineno-4-25" href="#__codelineno-4-25"></a><span class="w"> </span>server-id<span class="o">=</span><span class="nv">$INSTANCE_ID</span><span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-26" name="__codelineno-4-26" href="#__codelineno-4-26"></a><span class="w"> </span>ip-id<span class="o">=</span><span class="nv">$FLEXIP_ID</span><span class="w"> </span><span class="se">\</span>
<a id="__codelineno-4-27" name="__codelineno-4-27" href="#__codelineno-4-27"></a><span class="w"> </span><span class="nv">zone</span><span class="o">=</span>fr-par-2
<a id="__codelineno-4-28" name="__codelineno-4-28" href="#__codelineno-4-28"></a>
<a id="__codelineno-4-29" name="__codelineno-4-29" href="#__codelineno-4-29"></a><span class="nv">FLEXIP</span><span class="o">=</span><span class="k">$(</span>scw<span class="w"> </span>instance<span class="w"> </span>ip<span class="w"> </span>get<span class="w"> </span><span class="nv">$FLEXIP_ID</span><span class="w"> </span><span class="nv">zone</span><span class="o">=</span>fr-par-2<span class="w"> </span><span class="nv">output</span><span class="o">=</span>json<span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-r<span class="w"> </span><span class="s1">&#39;.address&#39;</span><span class="k">)</span>
<a id="__codelineno-4-30" name="__codelineno-4-30" href="#__codelineno-4-30"></a><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;Scaleway public IP: </span><span class="nv">$FLEXIP</span><span class="s2">&quot;</span>
</code></pre></div>
<hr />
<h2 id="step-3-wireguard-on-the-nuc">Step 3 — WireGuard on the NUC<a class="headerlink" href="#step-3-wireguard-on-the-nuc" title="Permanent link">&para;</a></h2>
<div class="highlight"><pre><span></span><code><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="c1"># /etc/wireguard/wg1.conf (separate from any existing VPN tunnel)</span>
<a id="__codelineno-5-2" name="__codelineno-5-2" href="#__codelineno-5-2"></a>cat<span class="w"> </span>&gt;<span class="w"> </span>/etc/wireguard/wg1.conf<span class="w"> </span><span class="s">&lt;&lt; EOF</span>
<a id="__codelineno-5-3" name="__codelineno-5-3" href="#__codelineno-5-3"></a><span class="s">[Interface]</span>
<a id="__codelineno-5-4" name="__codelineno-5-4" href="#__codelineno-5-4"></a><span class="s">Address = 10.200.0.2/24</span>
<a id="__codelineno-5-5" name="__codelineno-5-5" href="#__codelineno-5-5"></a><span class="s">PrivateKey = $(cat /etc/wireguard/nuc_wg_priv)</span>
<a id="__codelineno-5-6" name="__codelineno-5-6" href="#__codelineno-5-6"></a>
<a id="__codelineno-5-7" name="__codelineno-5-7" href="#__codelineno-5-7"></a><span class="s">[Peer]</span>
<a id="__codelineno-5-8" name="__codelineno-5-8" href="#__codelineno-5-8"></a><span class="s"># Scaleway GPU</span>
<a id="__codelineno-5-9" name="__codelineno-5-9" href="#__codelineno-5-9"></a><span class="s">PublicKey = $(cat /etc/wireguard/scaleway_wg_pub)</span>
<a id="__codelineno-5-10" name="__codelineno-5-10" href="#__codelineno-5-10"></a><span class="s">Endpoint = ${FLEXIP}:51820</span>
<a id="__codelineno-5-11" name="__codelineno-5-11" href="#__codelineno-5-11"></a><span class="s">AllowedIPs = 10.200.0.1/32</span>
<a id="__codelineno-5-12" name="__codelineno-5-12" href="#__codelineno-5-12"></a><span class="s">PersistentKeepalive = 25</span>
<a id="__codelineno-5-13" name="__codelineno-5-13" href="#__codelineno-5-13"></a><span class="s">EOF</span>
<a id="__codelineno-5-14" name="__codelineno-5-14" href="#__codelineno-5-14"></a>
<a id="__codelineno-5-15" name="__codelineno-5-15" href="#__codelineno-5-15"></a>chmod<span class="w"> </span><span class="m">600</span><span class="w"> </span>/etc/wireguard/wg1.conf
<a id="__codelineno-5-16" name="__codelineno-5-16" href="#__codelineno-5-16"></a>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>--now<span class="w"> </span>wg-quick@wg1
</code></pre></div>
<p>Test when the instance is running:
<div class="highlight"><pre><span></span><code><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a>ping<span class="w"> </span><span class="m">10</span>.200.0.1<span class="w"> </span><span class="c1"># WireGuard tunnel</span>
<a id="__codelineno-6-2" name="__codelineno-6-2" href="#__codelineno-6-2"></a>curl<span class="w"> </span>http://10.200.0.1:8188<span class="w"> </span><span class="c1"># ComfyUI</span>
</code></pre></div></p>
<hr />
<h2 id="step-4-gpu-proxy-seamless-on-demand-startup">Step 4 — gpu-proxy (seamless on-demand startup)<a class="headerlink" href="#step-4-gpu-proxy-seamless-on-demand-startup" title="Permanent link">&para;</a></h2>
<p>This Docker service runs on the NUC. It proxies to the GPU instance and auto-starts/stops it.</p>
<h3 id="aigpu-proxydocker-composeyml"><code>ai/gpu-proxy/docker-compose.yml</code><a class="headerlink" href="#aigpu-proxydocker-composeyml" title="Permanent link">&para;</a></h3>
<div class="highlight"><pre><span></span><code><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a><span class="nt">services</span><span class="p">:</span>
<a id="__codelineno-7-2" name="__codelineno-7-2" href="#__codelineno-7-2"></a><span class="w"> </span><span class="nt">gpu-proxy</span><span class="p">:</span>
<a id="__codelineno-7-3" name="__codelineno-7-3" href="#__codelineno-7-3"></a><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">.</span>
<a id="__codelineno-7-4" name="__codelineno-7-4" href="#__codelineno-7-4"></a><span class="w"> </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">gpu-proxy</span>
<a id="__codelineno-7-5" name="__codelineno-7-5" href="#__codelineno-7-5"></a><span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
<a id="__codelineno-7-6" name="__codelineno-7-6" href="#__codelineno-7-6"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">SCW_SECRET_KEY=${SCW_SECRET_KEY}</span>
<a id="__codelineno-7-7" name="__codelineno-7-7" href="#__codelineno-7-7"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">SCW_PROJECT_ID=${SCW_PROJECT_ID}</span>
<a id="__codelineno-7-8" name="__codelineno-7-8" href="#__codelineno-7-8"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">SCW_INSTANCE_ID=${SCW_INSTANCE_ID}</span>
<a id="__codelineno-7-9" name="__codelineno-7-9" href="#__codelineno-7-9"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">SCW_ZONE=fr-par-2</span>
<a id="__codelineno-7-10" name="__codelineno-7-10" href="#__codelineno-7-10"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">GPU_HOST=10.200.0.1</span>
<a id="__codelineno-7-11" name="__codelineno-7-11" href="#__codelineno-7-11"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">COMFYUI_PORT=8188</span>
<a id="__codelineno-7-12" name="__codelineno-7-12" href="#__codelineno-7-12"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">OLLAMA_PORT=11434</span>
<a id="__codelineno-7-13" name="__codelineno-7-13" href="#__codelineno-7-13"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">IDLE_TIMEOUT=2700</span><span class="w"> </span><span class="c1"># 45 min</span>
<a id="__codelineno-7-14" name="__codelineno-7-14" href="#__codelineno-7-14"></a><span class="w"> </span><span class="nt">ports</span><span class="p">:</span>
<a id="__codelineno-7-15" name="__codelineno-7-15" href="#__codelineno-7-15"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">&quot;8190:8190&quot;</span><span class="w"> </span><span class="c1"># ComfyUI proxy</span>
<a id="__codelineno-7-16" name="__codelineno-7-16" href="#__codelineno-7-16"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">&quot;8191:8191&quot;</span><span class="w"> </span><span class="c1"># Ollama proxy</span>
<a id="__codelineno-7-17" name="__codelineno-7-17" href="#__codelineno-7-17"></a><span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">unless-stopped</span>
<a id="__codelineno-7-18" name="__codelineno-7-18" href="#__codelineno-7-18"></a><span class="w"> </span><span class="nt">networks</span><span class="p">:</span>
<a id="__codelineno-7-19" name="__codelineno-7-19" href="#__codelineno-7-19"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">shared_backend</span>
<a id="__codelineno-7-20" name="__codelineno-7-20" href="#__codelineno-7-20"></a>
<a id="__codelineno-7-21" name="__codelineno-7-21" href="#__codelineno-7-21"></a><span class="nt">networks</span><span class="p">:</span>
<a id="__codelineno-7-22" name="__codelineno-7-22" href="#__codelineno-7-22"></a><span class="w"> </span><span class="nt">shared_backend</span><span class="p">:</span>
<a id="__codelineno-7-23" name="__codelineno-7-23" href="#__codelineno-7-23"></a><span class="w"> </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
</code></pre></div>
<h3 id="aigpu-proxyproxypy"><code>ai/gpu-proxy/proxy.py</code><a class="headerlink" href="#aigpu-proxyproxypy" title="Permanent link">&para;</a></h3>
<div class="highlight"><pre><span></span><code><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a><span class="sd">&quot;&quot;&quot;</span>
<a id="__codelineno-8-2" name="__codelineno-8-2" href="#__codelineno-8-2"></a><span class="sd">On-demand GPU proxy. Auto-starts the Scaleway instance on first request,</span>
<a id="__codelineno-8-3" name="__codelineno-8-3" href="#__codelineno-8-3"></a><span class="sd">shuts it down after IDLE_TIMEOUT seconds of inactivity.</span>
<a id="__codelineno-8-4" name="__codelineno-8-4" href="#__codelineno-8-4"></a><span class="sd">&quot;&quot;&quot;</span>
<a id="__codelineno-8-5" name="__codelineno-8-5" href="#__codelineno-8-5"></a><span class="kn">import</span><span class="w"> </span><span class="nn">asyncio</span><span class="o">,</span><span class="w"> </span><span class="nn">os</span><span class="o">,</span><span class="w"> </span><span class="nn">time</span><span class="o">,</span><span class="w"> </span><span class="nn">httpx</span><span class="o">,</span><span class="w"> </span><span class="nn">subprocess</span>
<a id="__codelineno-8-6" name="__codelineno-8-6" href="#__codelineno-8-6"></a><span class="kn">from</span><span class="w"> </span><span class="nn">fastapi</span><span class="w"> </span><span class="kn">import</span> <span class="n">FastAPI</span><span class="p">,</span> <span class="n">Request</span>
<a id="__codelineno-8-7" name="__codelineno-8-7" href="#__codelineno-8-7"></a><span class="kn">from</span><span class="w"> </span><span class="nn">fastapi.responses</span><span class="w"> </span><span class="kn">import</span> <span class="n">StreamingResponse</span><span class="p">,</span> <span class="n">JSONResponse</span>
<a id="__codelineno-8-8" name="__codelineno-8-8" href="#__codelineno-8-8"></a>
<a id="__codelineno-8-9" name="__codelineno-8-9" href="#__codelineno-8-9"></a><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
<a id="__codelineno-8-10" name="__codelineno-8-10" href="#__codelineno-8-10"></a>
<a id="__codelineno-8-11" name="__codelineno-8-11" href="#__codelineno-8-11"></a><span class="n">SCW_KEY</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&quot;SCW_SECRET_KEY&quot;</span><span class="p">]</span>
<a id="__codelineno-8-12" name="__codelineno-8-12" href="#__codelineno-8-12"></a><span class="n">SCW_PROJECT</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&quot;SCW_PROJECT_ID&quot;</span><span class="p">]</span>
<a id="__codelineno-8-13" name="__codelineno-8-13" href="#__codelineno-8-13"></a><span class="n">INSTANCE_ID</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&quot;SCW_INSTANCE_ID&quot;</span><span class="p">]</span>
<a id="__codelineno-8-14" name="__codelineno-8-14" href="#__codelineno-8-14"></a><span class="n">ZONE</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;SCW_ZONE&quot;</span><span class="p">,</span> <span class="s2">&quot;fr-par-2&quot;</span><span class="p">)</span>
<a id="__codelineno-8-15" name="__codelineno-8-15" href="#__codelineno-8-15"></a><span class="n">GPU_HOST</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;GPU_HOST&quot;</span><span class="p">,</span> <span class="s2">&quot;10.200.0.1&quot;</span><span class="p">)</span>
<a id="__codelineno-8-16" name="__codelineno-8-16" href="#__codelineno-8-16"></a><span class="n">COMFYUI_PORT</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;COMFYUI_PORT&quot;</span><span class="p">,</span> <span class="mi">8188</span><span class="p">))</span>
<a id="__codelineno-8-17" name="__codelineno-8-17" href="#__codelineno-8-17"></a><span class="n">OLLAMA_PORT</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;OLLAMA_PORT&quot;</span><span class="p">,</span> <span class="mi">11434</span><span class="p">))</span>
<a id="__codelineno-8-18" name="__codelineno-8-18" href="#__codelineno-8-18"></a><span class="n">IDLE_TIMEOUT</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;IDLE_TIMEOUT&quot;</span><span class="p">,</span> <span class="mi">2700</span><span class="p">))</span>
<a id="__codelineno-8-19" name="__codelineno-8-19" href="#__codelineno-8-19"></a>
<a id="__codelineno-8-20" name="__codelineno-8-20" href="#__codelineno-8-20"></a><span class="n">SCW_API</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;https://api.scaleway.com/instance/v1/zones/</span><span class="si">{</span><span class="n">ZONE</span><span class="si">}</span><span class="s2">&quot;</span>
<a id="__codelineno-8-21" name="__codelineno-8-21" href="#__codelineno-8-21"></a><span class="n">HEADERS</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;X-Auth-Token&quot;</span><span class="p">:</span> <span class="n">SCW_KEY</span><span class="p">,</span> <span class="s2">&quot;Content-Type&quot;</span><span class="p">:</span> <span class="s2">&quot;application/json&quot;</span><span class="p">}</span>
<a id="__codelineno-8-22" name="__codelineno-8-22" href="#__codelineno-8-22"></a>
<a id="__codelineno-8-23" name="__codelineno-8-23" href="#__codelineno-8-23"></a><span class="n">_state</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;last_activity&quot;</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="s2">&quot;starting&quot;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&quot;up&quot;</span><span class="p">:</span> <span class="kc">False</span><span class="p">}</span>
<a id="__codelineno-8-24" name="__codelineno-8-24" href="#__codelineno-8-24"></a><span class="n">_lock</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
<a id="__codelineno-8-25" name="__codelineno-8-25" href="#__codelineno-8-25"></a>
<a id="__codelineno-8-26" name="__codelineno-8-26" href="#__codelineno-8-26"></a>
<a id="__codelineno-8-27" name="__codelineno-8-27" href="#__codelineno-8-27"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_scw</span><span class="p">(</span><span class="n">method</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<a id="__codelineno-8-28" name="__codelineno-8-28" href="#__codelineno-8-28"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">httpx</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">()</span> <span class="k">as</span> <span class="n">c</span><span class="p">:</span>
<a id="__codelineno-8-29" name="__codelineno-8-29" href="#__codelineno-8-29"></a> <span class="n">r</span> <span class="o">=</span> <span class="k">await</span> <span class="n">c</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="n">method</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">SCW_API</span><span class="si">}{</span><span class="n">path</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">HEADERS</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<a id="__codelineno-8-30" name="__codelineno-8-30" href="#__codelineno-8-30"></a> <span class="n">r</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
<a id="__codelineno-8-31" name="__codelineno-8-31" href="#__codelineno-8-31"></a> <span class="k">return</span> <span class="n">r</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<a id="__codelineno-8-32" name="__codelineno-8-32" href="#__codelineno-8-32"></a>
<a id="__codelineno-8-33" name="__codelineno-8-33" href="#__codelineno-8-33"></a>
<a id="__codelineno-8-34" name="__codelineno-8-34" href="#__codelineno-8-34"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_instance_state</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<a id="__codelineno-8-35" name="__codelineno-8-35" href="#__codelineno-8-35"></a> <span class="n">d</span> <span class="o">=</span> <span class="k">await</span> <span class="n">_scw</span><span class="p">(</span><span class="s2">&quot;GET&quot;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;/servers/</span><span class="si">{</span><span class="n">INSTANCE_ID</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<a id="__codelineno-8-36" name="__codelineno-8-36" href="#__codelineno-8-36"></a> <span class="k">return</span> <span class="n">d</span><span class="p">[</span><span class="s2">&quot;server&quot;</span><span class="p">][</span><span class="s2">&quot;state&quot;</span><span class="p">]</span> <span class="c1"># running | stopped | stopping | starting</span>
<a id="__codelineno-8-37" name="__codelineno-8-37" href="#__codelineno-8-37"></a>
<a id="__codelineno-8-38" name="__codelineno-8-38" href="#__codelineno-8-38"></a>
<a id="__codelineno-8-39" name="__codelineno-8-39" href="#__codelineno-8-39"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_start_instance</span><span class="p">():</span>
<a id="__codelineno-8-40" name="__codelineno-8-40" href="#__codelineno-8-40"></a> <span class="k">await</span> <span class="n">_scw</span><span class="p">(</span><span class="s2">&quot;POST&quot;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;/servers/</span><span class="si">{</span><span class="n">INSTANCE_ID</span><span class="si">}</span><span class="s2">/action&quot;</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;action&quot;</span><span class="p">:</span> <span class="s2">&quot;poweron&quot;</span><span class="p">})</span>
<a id="__codelineno-8-41" name="__codelineno-8-41" href="#__codelineno-8-41"></a>
<a id="__codelineno-8-42" name="__codelineno-8-42" href="#__codelineno-8-42"></a>
<a id="__codelineno-8-43" name="__codelineno-8-43" href="#__codelineno-8-43"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_stop_instance</span><span class="p">():</span>
<a id="__codelineno-8-44" name="__codelineno-8-44" href="#__codelineno-8-44"></a> <span class="k">await</span> <span class="n">_scw</span><span class="p">(</span><span class="s2">&quot;POST&quot;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&quot;/servers/</span><span class="si">{</span><span class="n">INSTANCE_ID</span><span class="si">}</span><span class="s2">/action&quot;</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;action&quot;</span><span class="p">:</span> <span class="s2">&quot;poweroff&quot;</span><span class="p">})</span>
<a id="__codelineno-8-45" name="__codelineno-8-45" href="#__codelineno-8-45"></a>
<a id="__codelineno-8-46" name="__codelineno-8-46" href="#__codelineno-8-46"></a>
<a id="__codelineno-8-47" name="__codelineno-8-47" href="#__codelineno-8-47"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_wait_ready</span><span class="p">(</span><span class="n">host</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">180</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
<a id="__codelineno-8-48" name="__codelineno-8-48" href="#__codelineno-8-48"></a> <span class="n">deadline</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">+</span> <span class="n">timeout</span>
<a id="__codelineno-8-49" name="__codelineno-8-49" href="#__codelineno-8-49"></a> <span class="k">while</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">deadline</span><span class="p">:</span>
<a id="__codelineno-8-50" name="__codelineno-8-50" href="#__codelineno-8-50"></a> <span class="k">try</span><span class="p">:</span>
<a id="__codelineno-8-51" name="__codelineno-8-51" href="#__codelineno-8-51"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">httpx</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span> <span class="k">as</span> <span class="n">c</span><span class="p">:</span>
<a id="__codelineno-8-52" name="__codelineno-8-52" href="#__codelineno-8-52"></a> <span class="k">await</span> <span class="n">c</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;http://</span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s2">:</span><span class="si">{</span><span class="n">port</span><span class="si">}</span><span class="s2">/&quot;</span><span class="p">)</span>
<a id="__codelineno-8-53" name="__codelineno-8-53" href="#__codelineno-8-53"></a> <span class="k">return</span> <span class="kc">True</span>
<a id="__codelineno-8-54" name="__codelineno-8-54" href="#__codelineno-8-54"></a> <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
<a id="__codelineno-8-55" name="__codelineno-8-55" href="#__codelineno-8-55"></a> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<a id="__codelineno-8-56" name="__codelineno-8-56" href="#__codelineno-8-56"></a> <span class="k">return</span> <span class="kc">False</span>
<a id="__codelineno-8-57" name="__codelineno-8-57" href="#__codelineno-8-57"></a>
<a id="__codelineno-8-58" name="__codelineno-8-58" href="#__codelineno-8-58"></a>
<a id="__codelineno-8-59" name="__codelineno-8-59" href="#__codelineno-8-59"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">ensure_up</span><span class="p">(</span><span class="n">port</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
<a id="__codelineno-8-60" name="__codelineno-8-60" href="#__codelineno-8-60"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">_lock</span><span class="p">:</span>
<a id="__codelineno-8-61" name="__codelineno-8-61" href="#__codelineno-8-61"></a> <span class="k">if</span> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;up&quot;</span><span class="p">]:</span>
<a id="__codelineno-8-62" name="__codelineno-8-62" href="#__codelineno-8-62"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;last_activity&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<a id="__codelineno-8-63" name="__codelineno-8-63" href="#__codelineno-8-63"></a> <span class="k">return</span> <span class="kc">True</span>
<a id="__codelineno-8-64" name="__codelineno-8-64" href="#__codelineno-8-64"></a> <span class="k">if</span> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;starting&quot;</span><span class="p">]:</span>
<a id="__codelineno-8-65" name="__codelineno-8-65" href="#__codelineno-8-65"></a> <span class="k">return</span> <span class="kc">False</span> <span class="c1"># caller will retry</span>
<a id="__codelineno-8-66" name="__codelineno-8-66" href="#__codelineno-8-66"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;starting&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<a id="__codelineno-8-67" name="__codelineno-8-67" href="#__codelineno-8-67"></a>
<a id="__codelineno-8-68" name="__codelineno-8-68" href="#__codelineno-8-68"></a> <span class="k">try</span><span class="p">:</span>
<a id="__codelineno-8-69" name="__codelineno-8-69" href="#__codelineno-8-69"></a> <span class="n">state</span> <span class="o">=</span> <span class="k">await</span> <span class="n">_instance_state</span><span class="p">()</span>
<a id="__codelineno-8-70" name="__codelineno-8-70" href="#__codelineno-8-70"></a> <span class="k">if</span> <span class="n">state</span> <span class="o">!=</span> <span class="s2">&quot;running&quot;</span><span class="p">:</span>
<a id="__codelineno-8-71" name="__codelineno-8-71" href="#__codelineno-8-71"></a> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;[gpu-proxy] instance </span><span class="si">{</span><span class="n">state</span><span class="si">}</span><span class="s2"> → starting&quot;</span><span class="p">)</span>
<a id="__codelineno-8-72" name="__codelineno-8-72" href="#__codelineno-8-72"></a> <span class="k">await</span> <span class="n">_start_instance</span><span class="p">()</span>
<a id="__codelineno-8-73" name="__codelineno-8-73" href="#__codelineno-8-73"></a> <span class="c1"># wait for API to report running</span>
<a id="__codelineno-8-74" name="__codelineno-8-74" href="#__codelineno-8-74"></a> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">60</span><span class="p">):</span>
<a id="__codelineno-8-75" name="__codelineno-8-75" href="#__codelineno-8-75"></a> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<a id="__codelineno-8-76" name="__codelineno-8-76" href="#__codelineno-8-76"></a> <span class="k">if</span> <span class="k">await</span> <span class="n">_instance_state</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;running&quot;</span><span class="p">:</span>
<a id="__codelineno-8-77" name="__codelineno-8-77" href="#__codelineno-8-77"></a> <span class="k">break</span>
<a id="__codelineno-8-78" name="__codelineno-8-78" href="#__codelineno-8-78"></a> <span class="c1"># wait for service to respond</span>
<a id="__codelineno-8-79" name="__codelineno-8-79" href="#__codelineno-8-79"></a> <span class="k">if</span> <span class="k">await</span> <span class="n">_wait_ready</span><span class="p">(</span><span class="n">GPU_HOST</span><span class="p">,</span> <span class="n">port</span><span class="p">):</span>
<a id="__codelineno-8-80" name="__codelineno-8-80" href="#__codelineno-8-80"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">_lock</span><span class="p">:</span>
<a id="__codelineno-8-81" name="__codelineno-8-81" href="#__codelineno-8-81"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;up&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<a id="__codelineno-8-82" name="__codelineno-8-82" href="#__codelineno-8-82"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;last_activity&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<a id="__codelineno-8-83" name="__codelineno-8-83" href="#__codelineno-8-83"></a> <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;[gpu-proxy] GPU instance ready&quot;</span><span class="p">)</span>
<a id="__codelineno-8-84" name="__codelineno-8-84" href="#__codelineno-8-84"></a> <span class="k">return</span> <span class="kc">True</span>
<a id="__codelineno-8-85" name="__codelineno-8-85" href="#__codelineno-8-85"></a> <span class="k">return</span> <span class="kc">False</span>
<a id="__codelineno-8-86" name="__codelineno-8-86" href="#__codelineno-8-86"></a> <span class="k">finally</span><span class="p">:</span>
<a id="__codelineno-8-87" name="__codelineno-8-87" href="#__codelineno-8-87"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">_lock</span><span class="p">:</span>
<a id="__codelineno-8-88" name="__codelineno-8-88" href="#__codelineno-8-88"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;starting&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
<a id="__codelineno-8-89" name="__codelineno-8-89" href="#__codelineno-8-89"></a>
<a id="__codelineno-8-90" name="__codelineno-8-90" href="#__codelineno-8-90"></a>
<a id="__codelineno-8-91" name="__codelineno-8-91" href="#__codelineno-8-91"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">idle_watchdog</span><span class="p">():</span>
<a id="__codelineno-8-92" name="__codelineno-8-92" href="#__codelineno-8-92"></a> <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<a id="__codelineno-8-93" name="__codelineno-8-93" href="#__codelineno-8-93"></a> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">60</span><span class="p">)</span>
<a id="__codelineno-8-94" name="__codelineno-8-94" href="#__codelineno-8-94"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">_lock</span><span class="p">:</span>
<a id="__codelineno-8-95" name="__codelineno-8-95" href="#__codelineno-8-95"></a> <span class="k">if</span> <span class="ow">not</span> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;up&quot;</span><span class="p">]:</span>
<a id="__codelineno-8-96" name="__codelineno-8-96" href="#__codelineno-8-96"></a> <span class="k">continue</span>
<a id="__codelineno-8-97" name="__codelineno-8-97" href="#__codelineno-8-97"></a> <span class="n">idle</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;last_activity&quot;</span><span class="p">]</span>
<a id="__codelineno-8-98" name="__codelineno-8-98" href="#__codelineno-8-98"></a> <span class="k">if</span> <span class="n">idle</span> <span class="o">&gt;</span> <span class="n">IDLE_TIMEOUT</span><span class="p">:</span>
<a id="__codelineno-8-99" name="__codelineno-8-99" href="#__codelineno-8-99"></a> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;[gpu-proxy] idle </span><span class="si">{</span><span class="n">idle</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">s → stopping instance&quot;</span><span class="p">)</span>
<a id="__codelineno-8-100" name="__codelineno-8-100" href="#__codelineno-8-100"></a> <span class="k">try</span><span class="p">:</span>
<a id="__codelineno-8-101" name="__codelineno-8-101" href="#__codelineno-8-101"></a> <span class="k">await</span> <span class="n">_stop_instance</span><span class="p">()</span>
<a id="__codelineno-8-102" name="__codelineno-8-102" href="#__codelineno-8-102"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">_lock</span><span class="p">:</span>
<a id="__codelineno-8-103" name="__codelineno-8-103" href="#__codelineno-8-103"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;up&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
<a id="__codelineno-8-104" name="__codelineno-8-104" href="#__codelineno-8-104"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;last_activity&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="mf">0.0</span>
<a id="__codelineno-8-105" name="__codelineno-8-105" href="#__codelineno-8-105"></a> <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<a id="__codelineno-8-106" name="__codelineno-8-106" href="#__codelineno-8-106"></a> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;[gpu-proxy] stop error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<a id="__codelineno-8-107" name="__codelineno-8-107" href="#__codelineno-8-107"></a>
<a id="__codelineno-8-108" name="__codelineno-8-108" href="#__codelineno-8-108"></a>
<a id="__codelineno-8-109" name="__codelineno-8-109" href="#__codelineno-8-109"></a><span class="nd">@app</span><span class="o">.</span><span class="n">on_event</span><span class="p">(</span><span class="s2">&quot;startup&quot;</span><span class="p">)</span>
<a id="__codelineno-8-110" name="__codelineno-8-110" href="#__codelineno-8-110"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">startup</span><span class="p">():</span>
<a id="__codelineno-8-111" name="__codelineno-8-111" href="#__codelineno-8-111"></a> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">idle_watchdog</span><span class="p">())</span>
<a id="__codelineno-8-112" name="__codelineno-8-112" href="#__codelineno-8-112"></a> <span class="c1"># Probe: is the instance already running from a previous session?</span>
<a id="__codelineno-8-113" name="__codelineno-8-113" href="#__codelineno-8-113"></a> <span class="k">try</span><span class="p">:</span>
<a id="__codelineno-8-114" name="__codelineno-8-114" href="#__codelineno-8-114"></a> <span class="k">if</span> <span class="k">await</span> <span class="n">_instance_state</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;running&quot;</span><span class="p">:</span>
<a id="__codelineno-8-115" name="__codelineno-8-115" href="#__codelineno-8-115"></a> <span class="k">if</span> <span class="k">await</span> <span class="n">_wait_ready</span><span class="p">(</span><span class="n">GPU_HOST</span><span class="p">,</span> <span class="n">COMFYUI_PORT</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">):</span>
<a id="__codelineno-8-116" name="__codelineno-8-116" href="#__codelineno-8-116"></a> <span class="k">async</span> <span class="k">with</span> <span class="n">_lock</span><span class="p">:</span>
<a id="__codelineno-8-117" name="__codelineno-8-117" href="#__codelineno-8-117"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;up&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<a id="__codelineno-8-118" name="__codelineno-8-118" href="#__codelineno-8-118"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;last_activity&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<a id="__codelineno-8-119" name="__codelineno-8-119" href="#__codelineno-8-119"></a> <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;[gpu-proxy] GPU already up on startup&quot;</span><span class="p">)</span>
<a id="__codelineno-8-120" name="__codelineno-8-120" href="#__codelineno-8-120"></a> <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
<a id="__codelineno-8-121" name="__codelineno-8-121" href="#__codelineno-8-121"></a> <span class="k">pass</span>
<a id="__codelineno-8-122" name="__codelineno-8-122" href="#__codelineno-8-122"></a>
<a id="__codelineno-8-123" name="__codelineno-8-123" href="#__codelineno-8-123"></a>
<a id="__codelineno-8-124" name="__codelineno-8-124" href="#__codelineno-8-124"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">_proxy</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">Request</span><span class="p">,</span> <span class="n">host</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
<a id="__codelineno-8-125" name="__codelineno-8-125" href="#__codelineno-8-125"></a> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;last_activity&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<a id="__codelineno-8-126" name="__codelineno-8-126" href="#__codelineno-8-126"></a> <span class="n">ready</span> <span class="o">=</span> <span class="k">await</span> <span class="n">ensure_up</span><span class="p">(</span><span class="n">port</span><span class="p">)</span>
<a id="__codelineno-8-127" name="__codelineno-8-127" href="#__codelineno-8-127"></a> <span class="k">if</span> <span class="ow">not</span> <span class="n">ready</span><span class="p">:</span>
<a id="__codelineno-8-128" name="__codelineno-8-128" href="#__codelineno-8-128"></a> <span class="c1"># Starting up — keep trying for up to 3 min</span>
<a id="__codelineno-8-129" name="__codelineno-8-129" href="#__codelineno-8-129"></a> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">36</span><span class="p">):</span>
<a id="__codelineno-8-130" name="__codelineno-8-130" href="#__codelineno-8-130"></a> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<a id="__codelineno-8-131" name="__codelineno-8-131" href="#__codelineno-8-131"></a> <span class="k">if</span> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;up&quot;</span><span class="p">]:</span>
<a id="__codelineno-8-132" name="__codelineno-8-132" href="#__codelineno-8-132"></a> <span class="k">break</span>
<a id="__codelineno-8-133" name="__codelineno-8-133" href="#__codelineno-8-133"></a> <span class="k">else</span><span class="p">:</span>
<a id="__codelineno-8-134" name="__codelineno-8-134" href="#__codelineno-8-134"></a> <span class="k">return</span> <span class="n">JSONResponse</span><span class="p">({</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="s2">&quot;GPU instance failed to start&quot;</span><span class="p">},</span> <span class="mi">503</span><span class="p">)</span>
<a id="__codelineno-8-135" name="__codelineno-8-135" href="#__codelineno-8-135"></a>
<a id="__codelineno-8-136" name="__codelineno-8-136" href="#__codelineno-8-136"></a> <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;http://</span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s2">:</span><span class="si">{</span><span class="n">port</span><span class="si">}{</span><span class="n">request</span><span class="o">.</span><span class="n">url</span><span class="o">.</span><span class="n">path</span><span class="si">}</span><span class="s2">&quot;</span>
<a id="__codelineno-8-137" name="__codelineno-8-137" href="#__codelineno-8-137"></a> <span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">url</span><span class="o">.</span><span class="n">query</span><span class="p">:</span>
<a id="__codelineno-8-138" name="__codelineno-8-138" href="#__codelineno-8-138"></a> <span class="n">url</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&quot;?</span><span class="si">{</span><span class="n">request</span><span class="o">.</span><span class="n">url</span><span class="o">.</span><span class="n">query</span><span class="si">}</span><span class="s2">&quot;</span>
<a id="__codelineno-8-139" name="__codelineno-8-139" href="#__codelineno-8-139"></a> <span class="n">body</span> <span class="o">=</span> <span class="k">await</span> <span class="n">request</span><span class="o">.</span><span class="n">body</span><span class="p">()</span>
<a id="__codelineno-8-140" name="__codelineno-8-140" href="#__codelineno-8-140"></a> <span class="n">client</span> <span class="o">=</span> <span class="n">httpx</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">httpx</span><span class="o">.</span><span class="n">Timeout</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">connect</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span>
<a id="__codelineno-8-141" name="__codelineno-8-141" href="#__codelineno-8-141"></a> <span class="n">req</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">build_request</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">method</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span>
<a id="__codelineno-8-142" name="__codelineno-8-142" href="#__codelineno-8-142"></a> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">v</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
<a id="__codelineno-8-143" name="__codelineno-8-143" href="#__codelineno-8-143"></a> <span class="k">if</span> <span class="n">k</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">{</span><span class="s2">&quot;host&quot;</span><span class="p">,</span> <span class="s2">&quot;content-length&quot;</span><span class="p">}},</span>
<a id="__codelineno-8-144" name="__codelineno-8-144" href="#__codelineno-8-144"></a> <span class="n">content</span><span class="o">=</span><span class="n">body</span><span class="p">)</span>
<a id="__codelineno-8-145" name="__codelineno-8-145" href="#__codelineno-8-145"></a> <span class="k">try</span><span class="p">:</span>
<a id="__codelineno-8-146" name="__codelineno-8-146" href="#__codelineno-8-146"></a> <span class="n">resp</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">stream</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<a id="__codelineno-8-147" name="__codelineno-8-147" href="#__codelineno-8-147"></a> <span class="k">except</span> <span class="n">httpx</span><span class="o">.</span><span class="n">ConnectError</span><span class="p">:</span>
<a id="__codelineno-8-148" name="__codelineno-8-148" href="#__codelineno-8-148"></a> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">aclose</span><span class="p">()</span>
<a id="__codelineno-8-149" name="__codelineno-8-149" href="#__codelineno-8-149"></a> <span class="k">return</span> <span class="n">JSONResponse</span><span class="p">({</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="s2">&quot;GPU not reachable&quot;</span><span class="p">},</span> <span class="mi">502</span><span class="p">)</span>
<a id="__codelineno-8-150" name="__codelineno-8-150" href="#__codelineno-8-150"></a>
<a id="__codelineno-8-151" name="__codelineno-8-151" href="#__codelineno-8-151"></a> <span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">stream</span><span class="p">():</span>
<a id="__codelineno-8-152" name="__codelineno-8-152" href="#__codelineno-8-152"></a> <span class="k">async</span> <span class="k">for</span> <span class="n">chunk</span> <span class="ow">in</span> <span class="n">resp</span><span class="o">.</span><span class="n">aiter_raw</span><span class="p">():</span>
<a id="__codelineno-8-153" name="__codelineno-8-153" href="#__codelineno-8-153"></a> <span class="k">yield</span> <span class="n">chunk</span>
<a id="__codelineno-8-154" name="__codelineno-8-154" href="#__codelineno-8-154"></a> <span class="k">await</span> <span class="n">resp</span><span class="o">.</span><span class="n">aclose</span><span class="p">()</span>
<a id="__codelineno-8-155" name="__codelineno-8-155" href="#__codelineno-8-155"></a> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">aclose</span><span class="p">()</span>
<a id="__codelineno-8-156" name="__codelineno-8-156" href="#__codelineno-8-156"></a>
<a id="__codelineno-8-157" name="__codelineno-8-157" href="#__codelineno-8-157"></a> <span class="k">return</span> <span class="n">StreamingResponse</span><span class="p">(</span><span class="n">stream</span><span class="p">(),</span> <span class="n">status_code</span><span class="o">=</span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="p">,</span>
<a id="__codelineno-8-158" name="__codelineno-8-158" href="#__codelineno-8-158"></a> <span class="n">headers</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">),</span>
<a id="__codelineno-8-159" name="__codelineno-8-159" href="#__codelineno-8-159"></a> <span class="n">media_type</span><span class="o">=</span><span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;content-type&quot;</span><span class="p">))</span>
<a id="__codelineno-8-160" name="__codelineno-8-160" href="#__codelineno-8-160"></a>
<a id="__codelineno-8-161" name="__codelineno-8-161" href="#__codelineno-8-161"></a>
<a id="__codelineno-8-162" name="__codelineno-8-162" href="#__codelineno-8-162"></a><span class="nd">@app</span><span class="o">.</span><span class="n">api_route</span><span class="p">(</span><span class="s2">&quot;/status&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;GET&quot;</span><span class="p">])</span>
<a id="__codelineno-8-163" name="__codelineno-8-163" href="#__codelineno-8-163"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">status</span><span class="p">():</span>
<a id="__codelineno-8-164" name="__codelineno-8-164" href="#__codelineno-8-164"></a> <span class="k">try</span><span class="p">:</span>
<a id="__codelineno-8-165" name="__codelineno-8-165" href="#__codelineno-8-165"></a> <span class="n">scw_state</span> <span class="o">=</span> <span class="k">await</span> <span class="n">_instance_state</span><span class="p">()</span>
<a id="__codelineno-8-166" name="__codelineno-8-166" href="#__codelineno-8-166"></a> <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<a id="__codelineno-8-167" name="__codelineno-8-167" href="#__codelineno-8-167"></a> <span class="n">scw_state</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&quot;</span>
<a id="__codelineno-8-168" name="__codelineno-8-168" href="#__codelineno-8-168"></a> <span class="k">return</span> <span class="p">{</span><span class="s2">&quot;instance&quot;</span><span class="p">:</span> <span class="n">scw_state</span><span class="p">,</span> <span class="s2">&quot;proxy_up&quot;</span><span class="p">:</span> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;up&quot;</span><span class="p">],</span>
<a id="__codelineno-8-169" name="__codelineno-8-169" href="#__codelineno-8-169"></a> <span class="s2">&quot;idle_s&quot;</span><span class="p">:</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;last_activity&quot;</span><span class="p">])</span> <span class="k">if</span> <span class="n">_state</span><span class="p">[</span><span class="s2">&quot;up&quot;</span><span class="p">]</span> <span class="k">else</span> <span class="kc">None</span><span class="p">}</span>
<a id="__codelineno-8-170" name="__codelineno-8-170" href="#__codelineno-8-170"></a>
<a id="__codelineno-8-171" name="__codelineno-8-171" href="#__codelineno-8-171"></a>
<a id="__codelineno-8-172" name="__codelineno-8-172" href="#__codelineno-8-172"></a><span class="nd">@app</span><span class="o">.</span><span class="n">api_route</span><span class="p">(</span><span class="s2">&quot;/{path:path}&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;GET&quot;</span><span class="p">,</span><span class="s2">&quot;POST&quot;</span><span class="p">,</span><span class="s2">&quot;PUT&quot;</span><span class="p">,</span><span class="s2">&quot;DELETE&quot;</span><span class="p">,</span><span class="s2">&quot;OPTIONS&quot;</span><span class="p">,</span><span class="s2">&quot;PATCH&quot;</span><span class="p">])</span>
<a id="__codelineno-8-173" name="__codelineno-8-173" href="#__codelineno-8-173"></a><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">comfyui_proxy</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">Request</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
<a id="__codelineno-8-174" name="__codelineno-8-174" href="#__codelineno-8-174"></a> <span class="k">return</span> <span class="k">await</span> <span class="n">_proxy</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">GPU_HOST</span><span class="p">,</span> <span class="n">COMFYUI_PORT</span><span class="p">)</span>
</code></pre></div>
<h3 id="aigpu-proxydockerfile"><code>ai/gpu-proxy/Dockerfile</code><a class="headerlink" href="#aigpu-proxydockerfile" title="Permanent link">&para;</a></h3>
<div class="highlight"><pre><span></span><code><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a><span class="k">FROM</span><span class="w"> </span><span class="s">python:3.12-slim</span>
<a id="__codelineno-9-2" name="__codelineno-9-2" href="#__codelineno-9-2"></a><span class="k">RUN</span><span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>fastapi<span class="w"> </span>uvicorn<span class="w"> </span>httpx
<a id="__codelineno-9-3" name="__codelineno-9-3" href="#__codelineno-9-3"></a><span class="k">COPY</span><span class="w"> </span>proxy.py<span class="w"> </span>/app/proxy.py
<a id="__codelineno-9-4" name="__codelineno-9-4" href="#__codelineno-9-4"></a><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span>
<a id="__codelineno-9-5" name="__codelineno-9-5" href="#__codelineno-9-5"></a><span class="k">EXPOSE</span><span class="w"> </span><span class="s">8190 8191</span>
<a id="__codelineno-9-6" name="__codelineno-9-6" href="#__codelineno-9-6"></a><span class="k">CMD</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;uvicorn&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;proxy:app&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;--host&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;0.0.0.0&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;--port&quot;</span><span class="p">,</span><span class="w"> </span><span class="s2">&quot;8190&quot;</span><span class="p">]</span>
</code></pre></div>
<hr />
<h2 id="step-5-wire-up-nuc-services">Step 5 — Wire up NUC services<a class="headerlink" href="#step-5-wire-up-nuc-services" title="Permanent link">&para;</a></h2>
<p>Add to <code>ai/.env</code>:
<div class="highlight"><pre><span></span><code><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a><span class="nv">SCW_SECRET_KEY</span><span class="o">=</span>&lt;your-scaleway-api-key&gt;
<a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a><span class="nv">SCW_PROJECT_ID</span><span class="o">=</span>&lt;your-project-id&gt;
<a id="__codelineno-10-3" name="__codelineno-10-3" href="#__codelineno-10-3"></a><span class="nv">SCW_INSTANCE_ID</span><span class="o">=</span>&lt;instance-id-from-step-2&gt;
</code></pre></div></p>
<p>In <code>ai/lobehub.yml</code>:
<div class="highlight"><pre><span></span><code><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">&#39;COMFYUI_BASE_URL=http://gpu-proxy:8190&#39;</span>
</code></pre></div></p>
<p>In <code>ai/mcp-gateway/server.py</code> (SERVERS dict):
<div class="highlight"><pre><span></span><code><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a><span class="s2">&quot;comfyui&quot;</span><span class="p">:</span> <span class="p">{</span>
<a id="__codelineno-12-2" name="__codelineno-12-2" href="#__codelineno-12-2"></a> <span class="s2">&quot;static&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
<a id="__codelineno-12-3" name="__codelineno-12-3" href="#__codelineno-12-3"></a> <span class="s2">&quot;upstream&quot;</span><span class="p">:</span> <span class="s2">&quot;http://gpu-proxy:8190&quot;</span><span class="p">,</span>
<a id="__codelineno-12-4" name="__codelineno-12-4" href="#__codelineno-12-4"></a> <span class="s2">&quot;group&quot;</span><span class="p">:</span> <span class="s2">&quot;image&quot;</span><span class="p">,</span>
<a id="__codelineno-12-5" name="__codelineno-12-5" href="#__codelineno-12-5"></a><span class="p">},</span>
</code></pre></div></p>
<p>For Immich ML (optional), add to <code>immich/docker-compose.yml</code>:
<div class="highlight"><pre><span></span><code><a id="__codelineno-13-1" name="__codelineno-13-1" href="#__codelineno-13-1"></a><span class="nt">environment</span><span class="p">:</span>
<a id="__codelineno-13-2" name="__codelineno-13-2" href="#__codelineno-13-2"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">IMMICH_MACHINE_LEARNING_URL=http://gpu-proxy:8192</span><span class="w"> </span><span class="c1"># add a third port for ML</span>
</code></pre></div></p>
<hr />
<h2 id="step-6-deploy">Step 6 — Deploy<a class="headerlink" href="#step-6-deploy" title="Permanent link">&para;</a></h2>
<div class="highlight"><pre><span></span><code><a id="__codelineno-14-1" name="__codelineno-14-1" href="#__codelineno-14-1"></a><span class="c1"># Deploy the proxy</span>
<a id="__codelineno-14-2" name="__codelineno-14-2" href="#__codelineno-14-2"></a><span class="nb">cd</span><span class="w"> </span>/opt/stacks/ai/gpu-proxy
<a id="__codelineno-14-3" name="__codelineno-14-3" href="#__codelineno-14-3"></a>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d
<a id="__codelineno-14-4" name="__codelineno-14-4" href="#__codelineno-14-4"></a>
<a id="__codelineno-14-5" name="__codelineno-14-5" href="#__codelineno-14-5"></a><span class="c1"># Restart LobeChat and MCP gateway to pick up new env</span>
<a id="__codelineno-14-6" name="__codelineno-14-6" href="#__codelineno-14-6"></a><span class="nb">cd</span><span class="w"> </span>/opt/stacks/ai
<a id="__codelineno-14-7" name="__codelineno-14-7" href="#__codelineno-14-7"></a>docker<span class="w"> </span>compose<span class="w"> </span>-f<span class="w"> </span>lobehub.yml<span class="w"> </span>up<span class="w"> </span>-d<span class="w"> </span>--force-recreate<span class="w"> </span>lobe
<a id="__codelineno-14-8" name="__codelineno-14-8" href="#__codelineno-14-8"></a><span class="nb">cd</span><span class="w"> </span>mcp-gateway<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>docker<span class="w"> </span>compose<span class="w"> </span>up<span class="w"> </span>-d<span class="w"> </span>--force-recreate<span class="w"> </span>mcp-gateway
</code></pre></div>
<h3 id="what-the-user-experience-looks-like">What the user experience looks like<a class="headerlink" href="#what-the-user-experience-looks-like" title="Permanent link">&para;</a></h3>
<ol>
<li>Open LobeChat, generate an image → first request triggers auto-start</li>
<li>"Starting GPU…" — ComfyUI returns a brief wait (up to 90 s cold start)</li>
<li>Images generate normally; the proxy keeps the instance alive</li>
<li>After 45 min with no requests → instance stops automatically</li>
<li><code>/status</code> endpoint on port 8190: <code>{"instance":"stopped","proxy_up":false}</code></li>
</ol>
<hr />
<h2 id="quick-start-stop-scripts">Quick-start / stop scripts<a class="headerlink" href="#quick-start-stop-scripts" title="Permanent link">&para;</a></h2>
<div class="highlight"><pre><span></span><code><a id="__codelineno-15-1" name="__codelineno-15-1" href="#__codelineno-15-1"></a><span class="c1"># /usr/local/bin/nuclide-gpu (NUC helper)</span>
<a id="__codelineno-15-2" name="__codelineno-15-2" href="#__codelineno-15-2"></a><span class="c1">#!/bin/bash</span>
<a id="__codelineno-15-3" name="__codelineno-15-3" href="#__codelineno-15-3"></a><span class="nv">ZONE</span><span class="o">=</span>fr-par-2
<a id="__codelineno-15-4" name="__codelineno-15-4" href="#__codelineno-15-4"></a><span class="nv">ID</span><span class="o">=</span><span class="k">$(</span>docker<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>gpu-proxy<span class="w"> </span>env<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>SCW_INSTANCE_ID<span class="w"> </span><span class="p">|</span><span class="w"> </span>cut<span class="w"> </span>-d<span class="o">=</span><span class="w"> </span>-f2<span class="k">)</span>
<a id="__codelineno-15-5" name="__codelineno-15-5" href="#__codelineno-15-5"></a><span class="k">case</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span><span class="w"> </span><span class="k">in</span>
<a id="__codelineno-15-6" name="__codelineno-15-6" href="#__codelineno-15-6"></a><span class="w"> </span>start<span class="o">)</span><span class="w"> </span>scw<span class="w"> </span>instance<span class="w"> </span>server<span class="w"> </span>action<span class="w"> </span><span class="nv">action</span><span class="o">=</span>poweron<span class="w"> </span>server-id<span class="o">=</span><span class="nv">$ID</span><span class="w"> </span><span class="nv">zone</span><span class="o">=</span><span class="nv">$ZONE</span><span class="w"> </span><span class="p">;;</span>
<a id="__codelineno-15-7" name="__codelineno-15-7" href="#__codelineno-15-7"></a><span class="w"> </span>stop<span class="o">)</span><span class="w"> </span>scw<span class="w"> </span>instance<span class="w"> </span>server<span class="w"> </span>action<span class="w"> </span><span class="nv">action</span><span class="o">=</span>poweroff<span class="w"> </span>server-id<span class="o">=</span><span class="nv">$ID</span><span class="w"> </span><span class="nv">zone</span><span class="o">=</span><span class="nv">$ZONE</span><span class="w"> </span><span class="p">;;</span>
<a id="__codelineno-15-8" name="__codelineno-15-8" href="#__codelineno-15-8"></a><span class="w"> </span>status<span class="o">)</span><span class="w"> </span>curl<span class="w"> </span>-s<span class="w"> </span>http://localhost:8190/status<span class="w"> </span><span class="p">|</span><span class="w"> </span>python3<span class="w"> </span>-m<span class="w"> </span>json.tool<span class="w"> </span><span class="p">;;</span>
<a id="__codelineno-15-9" name="__codelineno-15-9" href="#__codelineno-15-9"></a><span class="w"> </span>*<span class="o">)</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;Usage: nuclide-gpu start|stop|status&quot;</span><span class="w"> </span><span class="p">;;</span>
<a id="__codelineno-15-10" name="__codelineno-15-10" href="#__codelineno-15-10"></a><span class="k">esac</span>
</code></pre></div>
<hr />
<h2 id="scaleway-firewall-security-groups">Scaleway firewall (security groups)<a class="headerlink" href="#scaleway-firewall-security-groups" title="Permanent link">&para;</a></h2>
<p>On the Scaleway instance, only expose WireGuard. All services bind to <code>10.200.0.1</code> (WireGuard IP):</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-16-1" name="__codelineno-16-1" href="#__codelineno-16-1"></a>ufw<span class="w"> </span>default<span class="w"> </span>deny<span class="w"> </span>incoming
<a id="__codelineno-16-2" name="__codelineno-16-2" href="#__codelineno-16-2"></a>ufw<span class="w"> </span>allow<span class="w"> </span><span class="m">51820</span>/udp<span class="w"> </span><span class="c1"># WireGuard from anywhere</span>
<a id="__codelineno-16-3" name="__codelineno-16-3" href="#__codelineno-16-3"></a>ufw<span class="w"> </span>allow<span class="w"> </span>from<span class="w"> </span><span class="m">10</span>.200.0.0/24<span class="w"> </span><span class="c1"># full access over tunnel</span>
<a id="__codelineno-16-4" name="__codelineno-16-4" href="#__codelineno-16-4"></a>ufw<span class="w"> </span><span class="nb">enable</span>
</code></pre></div>
<hr />
<h2 id="model-storage-layout-on-the-200-gb-volume">Model storage layout (on the 200 GB volume)<a class="headerlink" href="#model-storage-layout-on-the-200-gb-volume" title="Permanent link">&para;</a></h2>
<div class="highlight"><pre><span></span><code><a id="__codelineno-17-1" name="__codelineno-17-1" href="#__codelineno-17-1"></a>/mnt/models/
<a id="__codelineno-17-2" name="__codelineno-17-2" href="#__codelineno-17-2"></a>├── checkpoints/ # FLUX.1-dev, SDXL, etc.
<a id="__codelineno-17-3" name="__codelineno-17-3" href="#__codelineno-17-3"></a>├── vae/
<a id="__codelineno-17-4" name="__codelineno-17-4" href="#__codelineno-17-4"></a>├── loras/
<a id="__codelineno-17-5" name="__codelineno-17-5" href="#__codelineno-17-5"></a>├── controlnet/
<a id="__codelineno-17-6" name="__codelineno-17-6" href="#__codelineno-17-6"></a>├── ollama/ # Ollama model blobs
<a id="__codelineno-17-7" name="__codelineno-17-7" href="#__codelineno-17-7"></a>└── upscale_models/
</code></pre></div>
<p>FLUX.1-dev (FP16): ~24 GB<br />
FLUX.1-schnell (NF4): ~8 GB<br />
Ollama llama3:70b (Q4): ~40 GB — fits alongside FLUX on 200 GB with room for LoRAs.</p>
<p>To pre-download models after first boot:
<div class="highlight"><pre><span></span><code><a id="__codelineno-18-1" name="__codelineno-18-1" href="#__codelineno-18-1"></a>ssh<span class="w"> </span>root@10.200.0.1<span class="w"> </span><span class="c1"># via WireGuard</span>
<a id="__codelineno-18-2" name="__codelineno-18-2" href="#__codelineno-18-2"></a>docker<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>comfyui<span class="w"> </span>python3<span class="w"> </span>-c<span class="w"> </span><span class="s2">&quot;</span>
<a id="__codelineno-18-3" name="__codelineno-18-3" href="#__codelineno-18-3"></a><span class="s2">from huggingface_hub import hf_hub_download</span>
<a id="__codelineno-18-4" name="__codelineno-18-4" href="#__codelineno-18-4"></a><span class="s2">hf_hub_download(&#39;black-forest-labs/FLUX.1-dev&#39;, &#39;flux1-dev.safetensors&#39;,</span>
<a id="__codelineno-18-5" name="__codelineno-18-5" href="#__codelineno-18-5"></a><span class="s2"> local_dir=&#39;/root/ComfyUI/models/checkpoints&#39;)</span>
<a id="__codelineno-18-6" name="__codelineno-18-6" href="#__codelineno-18-6"></a><span class="s2">&quot;</span>
</code></pre></div></p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../..", "features": ["navigation.tabs", "navigation.sections", "navigation.expand", "navigation.top", "search.highlight", "search.suggest", "content.code.copy"], "search": "../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../assets/javascripts/bundle.79ae519e.min.js"></script>
</body>
</html>