Jeg faldt over davidfowl/aspire-ssh-deploy en søndag aften. Et lille repo, mindre end 500 linjer C#, hvor David Fowler — en af folkene bag Aspire — viser hvordan man bygger en publisher der tager en hel AppHost og sender den over SSH til en Linux-boks. Ikke Azure. Ikke Kubernetes. Bare en server med et SSH-login.
Det interessante var ikke at den findes. Det interessante var at den findes som eksempel. David havde ikke skrevet en officiel SSH-publisher som NuGet-pakke. Han havde lagt et repo op der siger: sådan her ser mønsteret ud, kopiér det.
Og i samme sekund kunne jeg se min egen version. Jeg har ikke brug for SSH. Jeg har brug for Coolify.
Modellerne matcher
Jeg kører min infrastruktur på en Hetzner-boks med Coolify som orkestrator. Det er der agentics.dk lever, det er der Keycloak lever, det er der alt mit hobby-kram bor. Hver gang jeg deployer et nyt projekt, gør jeg det samme: klikker mig gennem Coolifys UI, opretter et project, vælger et environment, peger på et Docker image, gemmer.
Da jeg så Davids repo, kiggede jeg på Coolifys datamodel og Aspire's samtidig. Det var den slags øjeblik hvor man får lyst til at tegne en pil mellem to ting:
| Aspire | Coolify |
|---|---|
| AppHost | Project |
| Aspire environment (Dev/Staging/Prod) | Environment |
| Container-resource | Application / service |
| Network | Destination |
Det er ikke et tilfælde. Det er den samme tankemodel. Én distributed application, mange miljøer, hver med et net af services. Aspire har den i hovedet, Coolify har den i databasen. Mellem dem mangler bare et stykke kode der mapper det ene over på det andet.
Frøet
Klokken 12:07 mandag den 24. maj startede jeg projektet. Jeg fyrede /product-cli-bootstrap op med præcis denne tekst — og jeg lader den stå på engelsk, som jeg skrev den, så blokken herunder er ord-for-ord det Claude faktisk fik:
Det var hele specifikationen. To links, en mapping, en intention. Resten skrev sig selv — eller rettere: skrev Claude og jeg sammen over de næste tre dage, igennem fem ADRs, elleve features og en del frem-og-tilbage om hvor publisheren skulle bryde, hvis tokenet manglede.
Det færdige slutmål er stadig kort nok til at stå på et postkort:
var builder = DistributedApplication.CreateBuilder(args);
var coolifyUrl = builder.AddParameter("coolify-url");
var coolifyToken = builder.AddParameter("coolify-token", secret: true);
builder
.WithCoolifyDeploy(coolifyUrl, coolifyToken)
.WithCoolifyDestination("homelab");
builder.Build().Run();
Og så:
aspire deploy -e Production
Det er det. AppHost'en pakkes som images, billederne skubbes til et registry, og Coolifys REST API får besked om at oprette projekter, environments og services. Hele AppHost'en på én kommando, til min egen server.
Hvorfor nu
For et år siden ville jeg ikke have skrevet en linje af det her selv. Jeg ville have ledt efter et standard værktøj — Kamal, Dokku, Caprover, et eller andet — og brugt en weekend på at konfigurere det. Standard software bliver brugt fordi det er billigere end at bygge selv. Det er regnestykket der ligger bag halvdelen af alle "buy"-beslutninger i softwareverdenen.
Vibekoding har ændret det regnestykke. Min publisher er omkring 1.500 linjer C# fordelt på tre projekter. Jeg sad ikke og skrev dem. Jeg sad og læste dem, mens en sub-agent fyldte huller ud baseret på ADRs vi havde skrevet sammen om morgenen. Det tog tre dage med pauser. Hvis jeg havde forsøgt at lære Kamal, ville jeg stadig være på trin to af tutorialen.
Det er hele pointen i den her serie: byg det selv. Ikke fordi du skal, men fordi det er blevet billigt nok at det er værd at overveje. Når David Fowler lægger et 500-linjers eksempel op, er det ikke fordi han forventer at folk laver pull requests til hans repo. Det er fordi han forventer at folk laver deres egen version.
Det er præcis hvad jeg gjorde.
Hvad mangler
Da jeg trykkede aspire deploy for første gang mod min rigtige Coolify, ramte jeg en blind plet i deres REST API. Coolify har et koncept der hedder destinations — det netværk en container hostes på — og man kan oprette dem i UI'et. Men der findes intet API til det. Min publisher gik i en uendelig løkke på E_COOLIFY_ENVIRONMENT_UPSERT_FAILED.
Det er den næste post i denne serie. Tre timer senere lå patchen i min lokale Coolify, og PR'en lå hos coollabsio/coolify.
