[{"data":1,"prerenderedAt":533},["ShallowReactive",2],{"page-\u002Fblog\u002Fcloud_notes\u002Fvscode_remote_sprites":3},{"id":4,"title":5,"body":6,"date":526,"description":17,"extension":527,"meta":528,"navigation":296,"path":529,"seo":530,"stem":531,"__hash__":532},"content\u002Fcloud_notes\u002Fvscode_remote_sprites.md","Using Sprite as a Remote Dev Box",{"type":7,"value":8,"toc":516},"minimark",[9,13,18,23,27,30,33,36,39,43,48,51,80,83,159,162,166,170,177,185,216,219,485,489,496,503,506,512],[10,11,5],"h1",{"id":12},"using-sprite-as-a-remote-dev-box",[14,15,17],"h5",{"id":16},"california-electricity-is-getting-too-expensive-to-self-host","California electricity is getting too expensive to self-host",[19,20,22],"h2",{"id":21},"why-not-just-use-my-own-pc","Why not just use my own PC?",[24,25,26],"p",{},"I have a small dream: to do my work from a booth at McDonald's. Just a cheap laptop, a coffee, and wifi that somehow always works.",[24,28,29],{},"The catch is hardware. My desktop, a 7800X3D box, is fast, but I can't carry it anywhere, and leaving it on 24\u002F7 to reach it remotely has one problem: California electricity isn't cheap.",[24,31,32],{},"What I really want is a box that lives somewhere else: native x86_64 to match the server, strong when I'm using it, and asleep, not billing me when I'm not. That's basically what a Sprite is.",[24,34,35],{},"Sprites are a Fly.io thing: persistent Linux VMs that boot in a second or two, keep their state on disk between sessions, and quietly fall asleep when you stop touching them. Billing is granular and metered straight from the kernel's own cgroup counters. While the box is asleep there's no runtime to meter, so the compute bill effectively stops. Picture an EC2 instance that creates instantly and costs you nothing to leave idle. So I gave it a try.",[24,37,38],{},"I also considered just renting an EC2 box. The billing never sat right, though: you provision a fixed amount of CPU and pay for it whether you're hammering it or it's idle, and the only way around that is remembering to stop and start the instance. Fly bills for what I actually use, down to the second, and the Sprite sleeps on its own when I walk away, so I pay for the work, not the waiting.",[19,40,42],{"id":41},"setting-up-the-sprite","Setting up the Sprite",[44,45,47],"h3",{"id":46},"sprite-side-setup","Sprite-side setup",[24,49,50],{},"Create a sprite.",[52,53,58],"pre",{"className":54,"code":55,"language":56,"meta":57,"style":57},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","sprite create --skip-console my-blog\n","bash","",[59,60,61],"code",{"__ignoreMap":57},[62,63,66,70,74,77],"span",{"class":64,"line":65},"line",1,[62,67,69],{"class":68},"sBMFI","sprite",[62,71,73],{"class":72},"sfazB"," create",[62,75,76],{"class":72}," --skip-console",[62,78,79],{"class":72}," my-blog\n",[24,81,82],{},"Install sshd and upload your SSH public key.",[52,84,86],{"className":54,"code":85,"language":56,"meta":57,"style":57},"sprite exec -s my-blog --file ~\u002F.ssh\u002Fid_ed25519.pub:\u002Ftmp\u002Fk.pub -- bash -c '\nsudo apt-get update\nsudo apt-get install -y openssh-server\ninstall -d -m700 ~\u002F.ssh\ncat \u002Ftmp\u002Fk.pub >> ~\u002F.ssh\u002Fauthorized_keys\nsort -u -o ~\u002F.ssh\u002Fauthorized_keys ~\u002F.ssh\u002Fauthorized_keys\nchmod 600 ~\u002F.ssh\u002Fauthorized_keys'\n",[59,87,88,120,126,132,138,144,150],{"__ignoreMap":57},[62,89,90,92,95,98,101,104,107,110,113,116],{"class":64,"line":65},[62,91,69],{"class":68},[62,93,94],{"class":72}," exec",[62,96,97],{"class":72}," -s",[62,99,100],{"class":72}," my-blog",[62,102,103],{"class":72}," --file",[62,105,106],{"class":72}," ~\u002F.ssh\u002Fid_ed25519.pub:\u002Ftmp\u002Fk.pub",[62,108,109],{"class":72}," --",[62,111,112],{"class":72}," bash",[62,114,115],{"class":72}," -c",[62,117,119],{"class":118},"sMK4o"," '\n",[62,121,123],{"class":64,"line":122},2,[62,124,125],{"class":72},"sudo apt-get update\n",[62,127,129],{"class":64,"line":128},3,[62,130,131],{"class":72},"sudo apt-get install -y openssh-server\n",[62,133,135],{"class":64,"line":134},4,[62,136,137],{"class":72},"install -d -m700 ~\u002F.ssh\n",[62,139,141],{"class":64,"line":140},5,[62,142,143],{"class":72},"cat \u002Ftmp\u002Fk.pub >> ~\u002F.ssh\u002Fauthorized_keys\n",[62,145,147],{"class":64,"line":146},6,[62,148,149],{"class":72},"sort -u -o ~\u002F.ssh\u002Fauthorized_keys ~\u002F.ssh\u002Fauthorized_keys\n",[62,151,153,156],{"class":64,"line":152},7,[62,154,155],{"class":72},"chmod 600 ~\u002F.ssh\u002Fauthorized_keys",[62,157,158],{"class":118},"'\n",[24,160,161],{},"That's it for the Sprite-side setup.",[19,163,165],{"id":164},"client-setup","Client setup",[44,167,169],{"id":168},"client-side-ssh-config","Client-side SSH config",[24,171,172,173,176],{},"Modify your ",[59,174,175],{},"~\u002F.ssh\u002Fconfig"," (or wherever your SSH config lives) and add the following:",[52,178,183],{"className":179,"code":181,"language":182},[180],"language-text","Host *.spr.internal\n    ControlMaster auto\n    ControlPath ~\u002F.ssh\u002Fcm-%r@%h:%p\n    ControlPersist 1m\n    User sprite\n    ProxyCommand bash -c 'n=${0%%.spr.internal}; sprite exec -s \"$n\" -- bash -c \"pgrep -x sshd >\u002Fdev\u002Fnull 2>&1 || { sudo service ssh start; sleep 1; }\" >&2 \u003C\u002Fdev\u002Fnull; exec sprite proxy -s \"$n\" -W :22' %h\n","text",[59,184,181],{"__ignoreMap":57},[24,186,187,188,191,192,195,196,199,200,203,204,207,208,211,212,215],{},"That ",[59,189,190],{},"ProxyCommand"," line is doing the real work, and it's worth unpacking, because it's the whole reason this feels seamless. Since the Sprite is asleep when I'm away, I can't just point ",[59,193,194],{},"ssh"," at it — there's nothing listening yet. So before the connection goes through, it pulls the sprite name out of the hostname (everything before ",[59,197,198],{},".spr.internal","), uses ",[59,201,202],{},"sprite exec"," to wake the box, and checks whether ",[59,205,206],{},"sshd"," is already running, starting it if it isn't. Only then does it hand off to ",[59,209,210],{},"sprite proxy",", which tunnels the connection to port 22. The upshot: I type ",[59,213,214],{},"ssh my-blog.spr.internal"," and the wake-up, the service start, and the tunnel all happen on their own. The box was asleep a second ago and I never had to think about it.",[24,217,218],{},"Test the connection with SSH:",[52,220,222],{"className":54,"code":221,"language":56,"meta":57,"style":57},"$ ssh my-blog.spr.internal\nEnter passphrase for key '\u002FUsers\u002F****\u002F.ssh\u002Fid_ed25519': \nWelcome to Ubuntu 25.10 (GNU\u002FLinux 6.12.91-fly x86_64)\n\n * Documentation:  https:\u002F\u002Fdocs.ubuntu.com\n * Management:     https:\u002F\u002Flandscape.canonical.com\n * Support:        https:\u002F\u002Fubuntu.com\u002Fpro\n\nThis system has been minimized by removing packages and content that are\nnot required on a system that users do not log into.\n\nTo restore this content, you can run the 'unminimize' command.\nLast login: Sat May 30 09:42:09 2026 from fdf::2\nsprite@my-blog:~$\n",[59,223,224,235,265,292,298,309,319,329,334,373,407,412,449,479],{"__ignoreMap":57},[62,225,226,229,232],{"class":64,"line":65},[62,227,228],{"class":68},"$",[62,230,231],{"class":72}," ssh",[62,233,234],{"class":72}," my-blog.spr.internal\n",[62,236,237,240,243,246,249,252,255,258,261],{"class":64,"line":122},[62,238,239],{"class":68},"Enter",[62,241,242],{"class":72}," passphrase",[62,244,245],{"class":72}," for",[62,247,248],{"class":72}," key",[62,250,251],{"class":118}," '",[62,253,254],{"class":72},"\u002FUsers\u002F****\u002F.ssh\u002Fid_ed25519",[62,256,257],{"class":118},"'",[62,259,260],{"class":72},":",[62,262,264],{"class":263},"sTEyZ"," \n",[62,266,267,270,273,276,280,283,286,289],{"class":64,"line":128},[62,268,269],{"class":68},"Welcome",[62,271,272],{"class":72}," to",[62,274,275],{"class":72}," Ubuntu",[62,277,279],{"class":278},"sbssI"," 25.10",[62,281,282],{"class":263}," (GNU\u002FLinux ",[62,284,285],{"class":72},"6.12.91-fly",[62,287,288],{"class":72}," x86_64",[62,290,291],{"class":263},")\n",[62,293,294],{"class":64,"line":134},[62,295,297],{"emptyLinePlaceholder":296},true,"\n",[62,299,300,303,306],{"class":64,"line":140},[62,301,302],{"class":68}," *",[62,304,305],{"class":72}," Documentation:",[62,307,308],{"class":72},"  https:\u002F\u002Fdocs.ubuntu.com\n",[62,310,311,313,316],{"class":64,"line":146},[62,312,302],{"class":68},[62,314,315],{"class":72}," Management:",[62,317,318],{"class":72},"     https:\u002F\u002Flandscape.canonical.com\n",[62,320,321,323,326],{"class":64,"line":152},[62,322,302],{"class":68},[62,324,325],{"class":72}," Support:",[62,327,328],{"class":72},"        https:\u002F\u002Fubuntu.com\u002Fpro\n",[62,330,332],{"class":64,"line":331},8,[62,333,297],{"emptyLinePlaceholder":296},[62,335,337,340,343,346,349,352,355,358,361,364,367,370],{"class":64,"line":336},9,[62,338,339],{"class":68},"This",[62,341,342],{"class":72}," system",[62,344,345],{"class":72}," has",[62,347,348],{"class":72}," been",[62,350,351],{"class":72}," minimized",[62,353,354],{"class":72}," by",[62,356,357],{"class":72}," removing",[62,359,360],{"class":72}," packages",[62,362,363],{"class":72}," and",[62,365,366],{"class":72}," content",[62,368,369],{"class":72}," that",[62,371,372],{"class":72}," are\n",[62,374,376,379,382,385,388,390,392,395,398,401,404],{"class":64,"line":375},10,[62,377,378],{"class":68},"not",[62,380,381],{"class":72}," required",[62,383,384],{"class":72}," on",[62,386,387],{"class":72}," a",[62,389,342],{"class":72},[62,391,369],{"class":72},[62,393,394],{"class":72}," users",[62,396,397],{"class":72}," do",[62,399,400],{"class":72}," not",[62,402,403],{"class":72}," log",[62,405,406],{"class":72}," into.\n",[62,408,410],{"class":64,"line":409},11,[62,411,297],{"emptyLinePlaceholder":296},[62,413,415,418,421,424,427,430,433,436,439,441,444,446],{"class":64,"line":414},12,[62,416,417],{"class":68},"To",[62,419,420],{"class":72}," restore",[62,422,423],{"class":72}," this",[62,425,426],{"class":72}," content,",[62,428,429],{"class":72}," you",[62,431,432],{"class":72}," can",[62,434,435],{"class":72}," run",[62,437,438],{"class":72}," the",[62,440,251],{"class":118},[62,442,443],{"class":72},"unminimize",[62,445,257],{"class":118},[62,447,448],{"class":72}," command.\n",[62,450,452,455,458,461,464,467,470,473,476],{"class":64,"line":451},13,[62,453,454],{"class":68},"Last",[62,456,457],{"class":72}," login:",[62,459,460],{"class":72}," Sat",[62,462,463],{"class":72}," May",[62,465,466],{"class":278}," 30",[62,468,469],{"class":72}," 09:42:09",[62,471,472],{"class":278}," 2026",[62,474,475],{"class":72}," from",[62,477,478],{"class":72}," fdf::2\n",[62,480,482],{"class":64,"line":481},14,[62,483,484],{"class":68},"sprite@my-blog:~$\n",[44,486,488],{"id":487},"connect-it-using-vs-code-ssh-remote","Connect it using VS Code SSH Remote",[24,490,491,492,495],{},"First, make sure you have the ",[59,493,494],{},"Remote - SSH"," extension installed. After that, we can connect to our sprite easily over SSH.",[24,497,498],{},[499,500],"img",{"alt":501,"src":502},"Connect to Sprite in VSCode","\u002Fcontent\u002Fcloud_notes\u002Fvscode_remote_sprites\u002FVSCode.png",[24,504,505],{},"After that, we are done! VSCode running on Sprite with little setup.",[24,507,508],{},[499,509],{"alt":510,"src":511},"VSCode Screenshot showing Fly kernel","\u002Fcontent\u002Fcloud_notes\u002Fvscode_remote_sprites\u002FVSCode2.png",[513,514,515],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":57,"searchDepth":122,"depth":122,"links":517},[518,519,522],{"id":21,"depth":122,"text":22},{"id":41,"depth":122,"text":42,"children":520},[521],{"id":46,"depth":128,"text":47},{"id":164,"depth":122,"text":165,"children":523},[524,525],{"id":168,"depth":128,"text":169},{"id":487,"depth":128,"text":488},"2026-05-30","md",{},"\u002Fcloud_notes\u002Fvscode_remote_sprites",{"title":5,"description":17},"cloud_notes\u002Fvscode_remote_sprites","grPuneCNPOlim06-OLkMdCjkvGHuaTyyjQb4H0Qh_Wk",1780145330983]