[{"data":1,"prerenderedAt":2828},["ShallowReactive",2],{"post:\u002Fblog\u002Farchitecture-from-idea-to-production":3,"page-globals":1450,"blog-related-src":1504},{"id":4,"title":5,"body":6,"canonical":1415,"category":1416,"date":1417,"description":1418,"extension":1419,"featured":157,"image":1420,"meta":1421,"navigation":157,"og":1422,"path":1427,"readingTime":698,"recommendation":1428,"seo":1436,"stem":1445,"tags":1446,"twitter":1447,"updated":1417,"__hash__":1449},"blog\u002Fblog\u002Farchitecture-from-idea-to-production.md","Od pomysłu do produkcji: architektura, która nie zawiedzie pod obciążeniem",{"type":7,"value":8,"toc":1399},"minimark",[9,13,16,21,30,33,39,43,46,54,231,237,245,250,253,256,259,272,277,280,353,358,361,364,368,371,374,423,428,445,448,451,465,468,471,474,494,497,501,504,507,534,537,542,1087,1092,1095,1098,1102,1105,1108,1119,1122,1136,1139,1153,1156,1160,1163,1180,1183,1190,1193,1196,1229,1233,1236,1247,1250,1259,1263,1266,1348,1353,1375,1378,1382,1385,1388,1392,1395],[10,11,12],"p",{},"Większość problemów produkcyjnych nie bierze się z braku umiejętności programistycznych. Bierze się z decyzji podjętych zbyt wcześnie, zbyt późno albo w ogóle. Dobra architektura to nie diagram — to ciąg świadomych wyborów, z których każdy da się uzasadnić i cofnąć, jeśli okaże się błędny.",[10,14,15],{},"Poniżej opisuję sposób, w jaki prowadzę projekt od pierwszej rozmowy po działającą produkcję. To nie jest przepis na sprzedaż — to metodyka, która ogranicza ryzyko.",[17,18,20],"h2",{"id":19},"zacznij-od-ograniczeń-nie-od-rozwiązania","Zacznij od ograniczeń, nie od rozwiązania",[10,22,23,24,29],{},"Zanim padnie słowo „mikroserwis\" albo „Kubernetes\", warto odpowiedzieć na kilka pytań (swoją drogą GitOps z wykorzystaniem VPS opisywałem w tym ",[25,26,28],"a",{"href":27},"\u002Fblog\u002Fgitops-on-vps-infrastructure","artykule","). Jaki problem biznesowy rozwiązujemy? Ile osób będzie ten system rozwijać? Jakie są realne wymagania co do dostępności i czasu odpowiedzi? Co się stanie, gdy usługa padnie na godzinę?",[10,31,32],{},"Odpowiedzi na te pytania eliminują większość „modnych\" rozwiązań szybciej niż jakakolwiek dyskusja o technologii. System obsługiwany przez trzyosobowy zespół rzadko potrzebuje architektury zaprojektowanej dla organizacji zatrudniającej setki inżynierów.",[34,35,36],"callout",{},[10,37,38],{},"Najdroższe decyzje architektoniczne to te, które podejmuje się na podstawie obecnych trendów — bo „tak się teraz robi\". Jeszcze droższe może okazać się nie podejmowanie żadnych decyzji architektonicznych.",[17,40,42],{"id":41},"granice-ważniejsze-niż-warstwy","Granice ważniejsze niż warstwy",[10,44,45],{},"Kiedy znam już ograniczenia, zaczynam od granic. Nie od warstw technicznych (kontroler, serwis, repozytorium), tylko od granic domenowych: co jest osobnym obszarem odpowiedzialności, a co można trzymać razem.",[10,47,48,49,53],{},"W praktyce najczęściej sięgam po ",[50,51,52],"strong",{},"modular monolith",": jeden proces wdrożeniowy, ale wyraźnie odseparowane moduły z jawnymi kontraktami. To daje większość korzyści z podziału na mikroserwisy, bez ich kosztu operacyjnego.",[55,56,61],"pre",{"className":57,"code":58,"language":59,"meta":60,"style":60},"language-csharp shiki shiki-themes one-dark-pro one-dark-pro","\u002F\u002F Moduł komunikuje się ze światem tylko przez jawny kontrakt.\n\u002F\u002F Przykład CQRS.\npublic interface IBillingModule\n{\n    \u002F\u002F Wszystkie żądania mutujące stan (commands) przechodzą przez tę metodę\n    Task\u003CIResult> ExecuteCommandAsync(\n      ICommand command, \n      CancellationToken ct);\n\n    \u002F\u002F Wszystkie zapytania niemutujące (queries) przechodzą przez tę metodę\n    Task\u003CIResult\u003CTResponse>> ExecuteQueryAsync\u003CTResult>(\n      IQuery\u003CTResult> query, \n      CancellationToken cancellationToken = default);\n}\n","csharp","",[62,63,64,73,79,93,100,106,128,140,152,159,165,193,210,225],"code",{"__ignoreMap":60},[65,66,69],"span",{"class":67,"line":68},"line",1,[65,70,72],{"class":71},"sIbxt","\u002F\u002F Moduł komunikuje się ze światem tylko przez jawny kontrakt.\n",[65,74,76],{"class":67,"line":75},2,[65,77,78],{"class":71},"\u002F\u002F Przykład CQRS.\n",[65,80,82,86,89],{"class":67,"line":81},3,[65,83,85],{"class":84},"sCyHM","public",[65,87,88],{"class":84}," interface",[65,90,92],{"class":91},"sZsuq"," IBillingModule\n",[65,94,96],{"class":67,"line":95},4,[65,97,99],{"class":98},"shasQ","{\n",[65,101,103],{"class":67,"line":102},5,[65,104,105],{"class":71},"    \u002F\u002F Wszystkie żądania mutujące stan (commands) przechodzą przez tę metodę\n",[65,107,109,112,115,118,121,125],{"class":67,"line":108},6,[65,110,111],{"class":91},"    Task",[65,113,114],{"class":98},"\u003C",[65,116,117],{"class":91},"IResult",[65,119,120],{"class":98},"> ",[65,122,124],{"class":123},"sw6ri","ExecuteCommandAsync",[65,126,127],{"class":98},"(\n",[65,129,131,134,137],{"class":67,"line":130},7,[65,132,133],{"class":91},"      ICommand",[65,135,136],{"class":91}," command",[65,138,139],{"class":98},", \n",[65,141,143,146,149],{"class":67,"line":142},8,[65,144,145],{"class":91},"      CancellationToken",[65,147,148],{"class":91}," ct",[65,150,151],{"class":98},");\n",[65,153,155],{"class":67,"line":154},9,[65,156,158],{"emptyLinePlaceholder":157},true,"\n",[65,160,162],{"class":67,"line":161},10,[65,163,164],{"class":71},"    \u002F\u002F Wszystkie zapytania niemutujące (queries) przechodzą przez tę metodę\n",[65,166,168,170,172,174,176,179,182,185,187,190],{"class":67,"line":167},11,[65,169,111],{"class":91},[65,171,114],{"class":98},[65,173,117],{"class":91},[65,175,114],{"class":98},[65,177,178],{"class":91},"TResponse",[65,180,181],{"class":98},">> ",[65,183,184],{"class":123},"ExecuteQueryAsync",[65,186,114],{"class":98},[65,188,189],{"class":91},"TResult",[65,191,192],{"class":98},">(\n",[65,194,196,199,201,203,205,208],{"class":67,"line":195},12,[65,197,198],{"class":91},"      IQuery",[65,200,114],{"class":98},[65,202,189],{"class":91},[65,204,120],{"class":98},[65,206,207],{"class":91},"query",[65,209,139],{"class":98},[65,211,213,215,218,222],{"class":67,"line":212},13,[65,214,145],{"class":91},[65,216,217],{"class":91}," cancellationToken",[65,219,221],{"class":220},"seqx8"," =",[65,223,224],{"class":98}," default);\n",[65,226,228],{"class":67,"line":227},14,[65,229,230],{"class":98},"}\n",[10,232,233,236],{},[234,235],"br",{},"\nDopóki komunikacja między modułami przechodzi przez takie kontrakty, podział na osobne usługi pozostaje decyzją wdrożeniową — a nie architektoniczną, którą trzeba podjąć na starcie.",[34,238,239],{},[10,240,241,242,244],{},"Stara mądrość głosi, że stosowanie architektury mikroserwisów ma uzasadnienie tylko wtedy, kiedy nie da się w danym przypadku zastosować modułowego monolitu.\n",[234,243],{},"\nPrawie każda aplikacja zaczyna swój żywot od monolitu, a skalowanie wymusza przejście na mikroserwisy.",[246,247,249],"h3",{"id":248},"komunikacja-między-modułami","Komunikacja między modułami",[10,251,252],{},"Sam podział kodu na moduły nie wystarcza. Jeżeli jeden moduł może odwołać się bezpośrednio do repozytorium, encji albo tabel należących do innego modułu, granica istnieje wyłącznie na diagramie.",[10,254,255],{},"Każdy moduł powinien być właścicielem swojego modelu, danych i reguł biznesowych. Pozostałe części systemu mogą komunikować się z nim wyłącznie przez jawnie zdefiniowane kontrakty.",[10,257,258],{},"W praktyce stosuję trzy podstawowe sposoby komunikacji między modułami:",[260,261,262,266,269],"ul",{},[263,264,265],"li",{},"synchroniczne wywołanie publicznego API modułu,",[263,267,268],{},"asynchroniczne zdarzenia integracyjne,",[263,270,271],{},"orkiestrację procesu przez osobną warstwę aplikacyjną lub process manager.",[273,274,276],"h4",{"id":275},"publiczne-api-modułu","Publiczne API modułu",[10,278,279],{},"Synchroniczne wywołanie sprawdza się wtedy, gdy moduł wywołujący potrzebuje odpowiedzi przed kontynuowaniem operacji. Przykładowo moduł odpowiedzialny za tworzenie zamówienia może zapytać moduł produktów o stany magazynowe lub zażądać zarezerwowania tych stanów.",[55,281,283],{"className":57,"code":282,"language":59,"meta":60,"style":60},"\u002F\u002F Publiczne API, dostępne do wykorzystania przez zewnętzne moduły\npublic interface IStockManagementModule\n{ \n    \u002F\u002F Metoda publicznego API modułu magazynowego.\n    \u002F\u002F Rezerwuje stany magazynowe produktów.\n    Task\u003CResult> ReserveProducts(\n      ReserveProductsRequest request, \n      CancellationToken cancellationToken); \n}\n",[62,284,285,290,299,304,309,314,330,340,349],{"__ignoreMap":60},[65,286,287],{"class":67,"line":68},[65,288,289],{"class":71},"\u002F\u002F Publiczne API, dostępne do wykorzystania przez zewnętzne moduły\n",[65,291,292,294,296],{"class":67,"line":75},[65,293,85],{"class":84},[65,295,88],{"class":84},[65,297,298],{"class":91}," IStockManagementModule\n",[65,300,301],{"class":67,"line":81},[65,302,303],{"class":98},"{ \n",[65,305,306],{"class":67,"line":95},[65,307,308],{"class":71},"    \u002F\u002F Metoda publicznego API modułu magazynowego.\n",[65,310,311],{"class":67,"line":102},[65,312,313],{"class":71},"    \u002F\u002F Rezerwuje stany magazynowe produktów.\n",[65,315,316,318,320,323,325,328],{"class":67,"line":108},[65,317,111],{"class":91},[65,319,114],{"class":98},[65,321,322],{"class":91},"Result",[65,324,120],{"class":98},[65,326,327],{"class":123},"ReserveProducts",[65,329,127],{"class":98},[65,331,332,335,338],{"class":67,"line":130},[65,333,334],{"class":91},"      ReserveProductsRequest",[65,336,337],{"class":91}," request",[65,339,139],{"class":98},[65,341,342,344,346],{"class":67,"line":142},[65,343,145],{"class":91},[65,345,217],{"class":91},[65,347,348],{"class":98},"); \n",[65,350,351],{"class":67,"line":154},[65,352,230],{"class":98},[10,354,355,357],{},[234,356],{},"\nPubliczny kontrakt powinien opisywać przypadki użycia udostępniane przez moduł, a nie jego wewnętrzną strukturę. Nie powinien zwracać encji domenowych ani udostępniać repozytoriów.",[10,359,360],{},"Najprościej zdefiniować jawne, typowane operacje zamiast jednego uniwersalnego interfejsu przyjmującego dowolne komendy i zapytania. Ogólny dispatcher CQRS może być użyteczny wewnątrz modułu, ale wystawiony jako jedyny publiczny kontrakt łatwo ukrywa rzeczywiste zależności.",[10,362,363],{},"Dzięki typowanemu API już na poziomie kodu widać, z jakich możliwości modułu korzystają pozostałe części systemu.",[273,365,367],{"id":366},"zdarzenia-domenowe-i-integracyjne","Zdarzenia domenowe i integracyjne",[10,369,370],{},"Nie każde działanie wymaga synchronicznej odpowiedzi. Jeżeli moduł jedynie informuje, że coś się wydarzyło, lepszym rozwiązaniem jest zdarzenie integracyjne.",[10,372,373],{},"Przykładowo po utworzeniu maszyny moduł provisioningu może opublikować zdarzenie:",[55,375,377],{"className":57,"code":376,"language":59,"meta":60,"style":60},"public sealed record VirtualMachineProvisioned(\n    Guid VirtualMachineId, \n    Guid CustomerId, \n    DateTimeOffset ProvisionedAt);\n",[62,378,379,394,404,413],{"__ignoreMap":60},[65,380,381,383,386,389,392],{"class":67,"line":68},[65,382,85],{"class":84},[65,384,385],{"class":84}," sealed",[65,387,388],{"class":84}," record",[65,390,391],{"class":91}," VirtualMachineProvisioned",[65,393,127],{"class":98},[65,395,396,399,402],{"class":67,"line":75},[65,397,398],{"class":91},"    Guid",[65,400,401],{"class":91}," VirtualMachineId",[65,403,139],{"class":98},[65,405,406,408,411],{"class":67,"line":81},[65,407,398],{"class":91},[65,409,410],{"class":91}," CustomerId",[65,412,139],{"class":98},[65,414,415,418,421],{"class":67,"line":95},[65,416,417],{"class":91},"    DateTimeOffset",[65,419,420],{"class":91}," ProvisionedAt",[65,422,151],{"class":98},[10,424,425,427],{},[234,426],{},"\nZdarzenie może zostać obsłużone niezależnie przez moduły odpowiedzialne za:",[260,429,430,433,436,439,442],{},[263,431,432],{},"rozliczenia,",[263,434,435],{},"powiadomienia,",[263,437,438],{},"audyt,",[263,440,441],{},"konfigurację monitoringu,",[263,443,444],{},"aktualizację panelu klienta.",[10,446,447],{},"Moduł publikujący nie powinien wiedzieć, ilu odbiorców istnieje ani jakie działania wykonują. Informuje jedynie o fakcie, który już nastąpił.",[10,449,450],{},"Ważne jest przy tym rozróżnienie dwóch rodzajów zdarzeń:",[260,452,453,459],{},[263,454,455,458],{},[50,456,457],{},"zdarzenia domenowe"," opisują zmiany wewnątrz pojedynczego modułu,",[263,460,461,464],{},[50,462,463],{},"zdarzenia integracyjne"," są stabilnym kontraktem przeznaczonym dla innych modułów lub usług.",[10,466,467],{},"Zdarzenie domenowe nie musi automatycznie stawać się zdarzeniem integracyjnym. Moduł może najpierw obsłużyć je wewnętrznie, a następnie opublikować uproszczony i stabilniejszy komunikat przeznaczony na zewnątrz.",[10,469,470],{},"W modularnym monolicie zdarzenia integracyjne mogą być początkowo obsługiwane wewnątrz tego samego procesu. Nie oznacza to jednak, że można pominąć problemy typowe dla komunikacji asynchronicznej.",[10,472,473],{},"Jeżeli publikacja zdarzenia musi być niezawodna, warto zastosować wzorce:",[260,475,476,482,488],{},[263,477,478,481],{},[50,479,480],{},"Outbox"," — zapis zmiany stanu i komunikatu w tej samej transakcji,",[263,483,484,487],{},[50,485,486],{},"Inbox"," — ochrona konsumenta przed wielokrotnym przetworzeniem tego samego komunikatu,",[263,489,490,493],{},[50,491,492],{},"idempotent consumer"," — możliwość bezpiecznego ponowienia obsługi zdarzenia.",[10,495,496],{},"Dzięki temu lokalny event bus może zostać później zastąpiony brokerem, na przykład RabbitMQ obsługiwanym przez MassTransit, bez zmiany podstawowego modelu komunikacji.",[273,498,500],{"id":499},"orkiestracja-procesów","Orkiestracja procesów",[10,502,503],{},"Niektóre operacje obejmują kilka modułów i składają się z wielu kroków. Nie powinny być wtedy implementowane jako łańcuch bezpośrednich wywołań pomiędzy modułami.",[10,505,506],{},"Przykładem może być proces utworzenia maszyny:",[508,509,510,513,516,519,522,525,528,531],"ol",{},[263,511,512],{},"Rezerwacja identyfikatora maszyny.",[263,514,515],{},"Skopiowanie szablonu.",[263,517,518],{},"Zastosowanie konfiguracji.",[263,520,521],{},"Rezerwacja adresu IP.",[263,523,524],{},"Utworzenie polityki kopii zapasowej.",[263,526,527],{},"Uruchomienie maszyny.",[263,529,530],{},"Aktywacja rozliczenia,",[263,532,533],{},"Wysłanie powiadomienia.",[10,535,536],{},"Odpowiedzialność za taki proces powinna należeć do dedykowanego koordynatora, na przykład application service, process managera albo sagi.",[34,538,539],{},[10,540,541],{},"Saga to popularny wzorzec architektoniczny służący do zarządzania transakcjami w systemach rozproszonych i architekturach mikroserwisów. Jego zadaniem jest orkiestrowanie i nadzór nad choreografią procesów rozproszonych.",[55,543,545],{"className":57,"code":544,"language":59,"meta":60,"style":60},"public sealed class ProvisionVirtualMachineProcess \n{ \n  private readonly IVirtualMachinesModule _virtualMachines;\n  private readonly IIpAddressManagementModule _ipam;\n  private readonly IBillingModule _billing;\n  \n  public ProvisionVirtualMachineProcess(\n    IVirtualMachinesModule virtualMachines,\n    IIpAddressManagementModule ipam,\n    IBillingModule billing)\n  {\n    _virtualMachines = virtualMachines;\n    _ipam = ipam;\n    _billing = billing;\n  }\n\n  public async Task\u003CResult> ExecuteAsync(\n    ProvisionVirtualMachine command, \n    CancellationToken cancellationToken) \n  { \n    var machine = await _virtualMachines.CreateAsync(command, cancellationToken);\n\n    if (machine.IsFailure) \n      return machine.Error; \n    \n    var address = await _ipam.ReserveAddressAsync(\n      new ReserveIpAddress(machine.Value.Id), \n      cancellationToken);\n    \n    if (address.IsFailure) \n    { \n      await _virtualMachines.MarkProvisioningAsFailedAsync(\n        machine.Value.Id, \n        address.Error, \n        cancellationToken); \n      \n      return address.Error; \n    }\n\n    await _billing.ActivateServiceAsync(\n      machine.Value.Id, \n      command.CustomerId, \n      cancellationToken);\n\n    return Result.Success(); \n  }\n}\n",[62,546,547,562,566,584,598,612,617,626,637,647,658,663,674,685,696,702,707,729,739,750,756,793,798,817,833,839,861,887,895,900,916,922,937,953,965,973,979,992,998,1003,1019,1035,1048,1055,1060,1077,1082],{"__ignoreMap":60},[65,548,549,551,553,556,559],{"class":67,"line":68},[65,550,85],{"class":84},[65,552,385],{"class":84},[65,554,555],{"class":84}," class",[65,557,558],{"class":91}," ProvisionVirtualMachineProcess",[65,560,561],{"class":98}," \n",[65,563,564],{"class":67,"line":75},[65,565,303],{"class":98},[65,567,568,571,574,577,581],{"class":67,"line":81},[65,569,570],{"class":84},"  private",[65,572,573],{"class":84}," readonly",[65,575,576],{"class":91}," IVirtualMachinesModule",[65,578,580],{"class":579},"sh-OU"," _virtualMachines",[65,582,583],{"class":98},";\n",[65,585,586,588,590,593,596],{"class":67,"line":95},[65,587,570],{"class":84},[65,589,573],{"class":84},[65,591,592],{"class":91}," IIpAddressManagementModule",[65,594,595],{"class":579}," _ipam",[65,597,583],{"class":98},[65,599,600,602,604,607,610],{"class":67,"line":102},[65,601,570],{"class":84},[65,603,573],{"class":84},[65,605,606],{"class":91}," IBillingModule",[65,608,609],{"class":579}," _billing",[65,611,583],{"class":98},[65,613,614],{"class":67,"line":108},[65,615,616],{"class":98},"  \n",[65,618,619,622,624],{"class":67,"line":130},[65,620,621],{"class":84},"  public",[65,623,558],{"class":123},[65,625,127],{"class":98},[65,627,628,631,634],{"class":67,"line":142},[65,629,630],{"class":91},"    IVirtualMachinesModule",[65,632,633],{"class":91}," virtualMachines",[65,635,636],{"class":98},",\n",[65,638,639,642,645],{"class":67,"line":154},[65,640,641],{"class":91},"    IIpAddressManagementModule",[65,643,644],{"class":91}," ipam",[65,646,636],{"class":98},[65,648,649,652,655],{"class":67,"line":161},[65,650,651],{"class":91},"    IBillingModule",[65,653,654],{"class":91}," billing",[65,656,657],{"class":98},")\n",[65,659,660],{"class":67,"line":167},[65,661,662],{"class":98},"  {\n",[65,664,665,668,670,672],{"class":67,"line":195},[65,666,667],{"class":579},"    _virtualMachines",[65,669,221],{"class":220},[65,671,633],{"class":579},[65,673,583],{"class":98},[65,675,676,679,681,683],{"class":67,"line":212},[65,677,678],{"class":579},"    _ipam",[65,680,221],{"class":220},[65,682,644],{"class":579},[65,684,583],{"class":98},[65,686,687,690,692,694],{"class":67,"line":227},[65,688,689],{"class":579},"    _billing",[65,691,221],{"class":220},[65,693,654],{"class":579},[65,695,583],{"class":98},[65,697,699],{"class":67,"line":698},15,[65,700,701],{"class":98},"  }\n",[65,703,705],{"class":67,"line":704},16,[65,706,158],{"emptyLinePlaceholder":157},[65,708,710,712,715,718,720,722,724,727],{"class":67,"line":709},17,[65,711,621],{"class":84},[65,713,714],{"class":84}," async",[65,716,717],{"class":91}," Task",[65,719,114],{"class":98},[65,721,322],{"class":91},[65,723,120],{"class":98},[65,725,726],{"class":123},"ExecuteAsync",[65,728,127],{"class":98},[65,730,732,735,737],{"class":67,"line":731},18,[65,733,734],{"class":91},"    ProvisionVirtualMachine",[65,736,136],{"class":91},[65,738,139],{"class":98},[65,740,742,745,747],{"class":67,"line":741},19,[65,743,744],{"class":91},"    CancellationToken",[65,746,217],{"class":91},[65,748,749],{"class":98},") \n",[65,751,753],{"class":67,"line":752},20,[65,754,755],{"class":98},"  { \n",[65,757,759,762,765,767,770,773,776,779,782,785,788,791],{"class":67,"line":758},21,[65,760,761],{"class":84},"    var",[65,763,764],{"class":579}," machine",[65,766,221],{"class":220},[65,768,769],{"class":98}," await ",[65,771,772],{"class":91},"_virtualMachines",[65,774,775],{"class":98},".",[65,777,778],{"class":123},"CreateAsync",[65,780,781],{"class":98},"(",[65,783,784],{"class":579},"command",[65,786,787],{"class":98},", ",[65,789,790],{"class":579},"cancellationToken",[65,792,151],{"class":98},[65,794,796],{"class":67,"line":795},22,[65,797,158],{"emptyLinePlaceholder":157},[65,799,801,804,807,810,812,815],{"class":67,"line":800},23,[65,802,803],{"class":84},"    if",[65,805,806],{"class":98}," (",[65,808,809],{"class":91},"machine",[65,811,775],{"class":98},[65,813,814],{"class":91},"IsFailure",[65,816,749],{"class":98},[65,818,820,823,825,827,830],{"class":67,"line":819},24,[65,821,822],{"class":84},"      return",[65,824,764],{"class":91},[65,826,775],{"class":98},[65,828,829],{"class":91},"Error",[65,831,832],{"class":98},"; \n",[65,834,836],{"class":67,"line":835},25,[65,837,838],{"class":98},"    \n",[65,840,842,844,847,849,851,854,856,859],{"class":67,"line":841},26,[65,843,761],{"class":84},[65,845,846],{"class":579}," address",[65,848,221],{"class":220},[65,850,769],{"class":98},[65,852,853],{"class":91},"_ipam",[65,855,775],{"class":98},[65,857,858],{"class":123},"ReserveAddressAsync",[65,860,127],{"class":98},[65,862,864,867,870,872,874,876,879,881,884],{"class":67,"line":863},27,[65,865,866],{"class":98},"      new ",[65,868,869],{"class":91},"ReserveIpAddress",[65,871,781],{"class":98},[65,873,809],{"class":91},[65,875,775],{"class":98},[65,877,878],{"class":91},"Value",[65,880,775],{"class":98},[65,882,883],{"class":91},"Id",[65,885,886],{"class":98},"), \n",[65,888,890,893],{"class":67,"line":889},28,[65,891,892],{"class":579},"      cancellationToken",[65,894,151],{"class":98},[65,896,898],{"class":67,"line":897},29,[65,899,838],{"class":98},[65,901,903,905,907,910,912,914],{"class":67,"line":902},30,[65,904,803],{"class":84},[65,906,806],{"class":98},[65,908,909],{"class":91},"address",[65,911,775],{"class":98},[65,913,814],{"class":91},[65,915,749],{"class":98},[65,917,919],{"class":67,"line":918},31,[65,920,921],{"class":98},"    { \n",[65,923,925,928,930,932,935],{"class":67,"line":924},32,[65,926,927],{"class":98},"      await ",[65,929,772],{"class":91},[65,931,775],{"class":98},[65,933,934],{"class":123},"MarkProvisioningAsFailedAsync",[65,936,127],{"class":98},[65,938,940,943,945,947,949,951],{"class":67,"line":939},33,[65,941,942],{"class":91},"        machine",[65,944,775],{"class":98},[65,946,878],{"class":91},[65,948,775],{"class":98},[65,950,883],{"class":91},[65,952,139],{"class":98},[65,954,956,959,961,963],{"class":67,"line":955},34,[65,957,958],{"class":91},"        address",[65,960,775],{"class":98},[65,962,829],{"class":91},[65,964,139],{"class":98},[65,966,968,971],{"class":67,"line":967},35,[65,969,970],{"class":579},"        cancellationToken",[65,972,348],{"class":98},[65,974,976],{"class":67,"line":975},36,[65,977,978],{"class":98},"      \n",[65,980,982,984,986,988,990],{"class":67,"line":981},37,[65,983,822],{"class":84},[65,985,846],{"class":91},[65,987,775],{"class":98},[65,989,829],{"class":91},[65,991,832],{"class":98},[65,993,995],{"class":67,"line":994},38,[65,996,997],{"class":98},"    }\n",[65,999,1001],{"class":67,"line":1000},39,[65,1002,158],{"emptyLinePlaceholder":157},[65,1004,1006,1009,1012,1014,1017],{"class":67,"line":1005},40,[65,1007,1008],{"class":98},"    await ",[65,1010,1011],{"class":91},"_billing",[65,1013,775],{"class":98},[65,1015,1016],{"class":123},"ActivateServiceAsync",[65,1018,127],{"class":98},[65,1020,1022,1025,1027,1029,1031,1033],{"class":67,"line":1021},41,[65,1023,1024],{"class":91},"      machine",[65,1026,775],{"class":98},[65,1028,878],{"class":91},[65,1030,775],{"class":98},[65,1032,883],{"class":91},[65,1034,139],{"class":98},[65,1036,1038,1041,1043,1046],{"class":67,"line":1037},42,[65,1039,1040],{"class":91},"      command",[65,1042,775],{"class":98},[65,1044,1045],{"class":91},"CustomerId",[65,1047,139],{"class":98},[65,1049,1051,1053],{"class":67,"line":1050},43,[65,1052,892],{"class":579},[65,1054,151],{"class":98},[65,1056,1058],{"class":67,"line":1057},44,[65,1059,158],{"emptyLinePlaceholder":157},[65,1061,1063,1066,1069,1071,1074],{"class":67,"line":1062},45,[65,1064,1065],{"class":84},"    return",[65,1067,1068],{"class":91}," Result",[65,1070,775],{"class":98},[65,1072,1073],{"class":123},"Success",[65,1075,1076],{"class":98},"(); \n",[65,1078,1080],{"class":67,"line":1079},46,[65,1081,701],{"class":98},[65,1083,1085],{"class":67,"line":1084},47,[65,1086,230],{"class":98},[10,1088,1089,1091],{},[234,1090],{},"\nKoordynator zna przebieg procesu, ale nie przejmuje logiki domenowej lub aplikacyjnej poszczególnych modułów. Każdy moduł nadal samodzielnie odpowiada za swoje reguły i stan.",[10,1093,1094],{},"Dla krótkich operacji wykonywanych w jednej transakcji wystarczy zwykły application service. Dla procesów długotrwałych, obejmujących operacje zewnętrzne, ponowienia i kompensacje lepiej sprawdzi się trwały process manager albo saga.",[10,1096,1097],{},"Stan takiego procesu powinien być zapisany. Dzięki temu awaria procesu, restart aplikacji albo chwilowa niedostępność zewnętrznego systemu nie powodują utraty informacji o aktualnym etapie operacji.",[273,1099,1101],{"id":1100},"kiedy-stosować-poszczególne-mechanizmy","Kiedy stosować poszczególne mechanizmy",[10,1103,1104],{},"Połączenie całej tej wiedzy i odpowiednie stosowanie wzorców wymaga wprawy i doświadczenia. Dlatego poniżej krótkie podsumowanie kiedy stosować poszczególne wzorce.",[10,1106,1107],{},"Publiczne API modułu wybieram wtedy, gdy:",[260,1109,1110,1113,1116],{},[263,1111,1112],{},"odpowiedź jest potrzebna natychmiast,",[263,1114,1115],{},"operacja wpływa na dalszy przebieg bieżącego przypadku użycia,",[263,1117,1118],{},"odmowa wykonania operacji powinna przerwać proces.",[10,1120,1121],{},"Zdarzenie integracyjne wybieram wtedy, gdy:",[260,1123,1124,1127,1130,1133],{},[263,1125,1126],{},"publikowany jest fakt, który już nastąpił,",[263,1128,1129],{},"nadawca nie potrzebuje natychmiastowej odpowiedzi,",[263,1131,1132],{},"odbiorców może być wielu,",[263,1134,1135],{},"dopuszczalna jest spójność ostateczna (eventual consistency).",[10,1137,1138],{},"Orkiestrację wybieram wtedy, gdy:",[260,1140,1141,1144,1147,1150],{},[263,1142,1143],{},"przypadek użycia obejmuje kilka modułów,",[263,1145,1146],{},"proces ma wiele etapów,",[263,1148,1149],{},"potrzebne są ponowienia, timeouty lub działania kompensacyjne,",[263,1151,1152],{},"stan procesu musi przetrwać restart aplikacji.",[10,1154,1155],{},"Często najlepszym rozwiązaniem jest połączenie tych mechanizmów. Koordynator może synchronicznie wywołać operacje niezbędne do kontynuowania procesu, a po jego zakończeniu opublikować zdarzenie integracyjne dla działań ubocznych.",[273,1157,1159],{"id":1158},"granice-których-nie-należy-przekraczać","Granice, których nie należy przekraczać",[10,1161,1162],{},"Niezależnie od wybranego mechanizmu moduły nie powinny:",[260,1164,1165,1168,1171,1174,1177],{},[263,1166,1167],{},"odwoływać się bezpośrednio do tabel innych modułów,",[263,1169,1170],{},"korzystać z ich repozytoriów,",[263,1172,1173],{},"modyfikować ich agregatów lub encji,",[263,1175,1176],{},"współdzielić modeli domenowych,",[263,1178,1179],{},"opierać komunikacji na szczegółach implementacyjnych.",[10,1181,1182],{},"Współdzielone mogą być niewielkie elementy techniczne, takie jak Result, identyfikatory, abstrakcje zegara albo mechanizmy publikowania komunikatów. Współdzielony kod nie powinien jednak stawać się miejscem, do którego trafia logika biznesowa należąca do konkretnych modułów.",[10,1184,1185,1186,1189],{},"W przypadku integracji z systemem zewnętrznym lub starszą częścią aplikacji warto dodatkowo zastosować ",[50,1187,1188],{},"warstwę antykorupcyjną",". Tłumaczy ona zewnętrzny model na pojęcia używane przez moduł i zapobiega przenikaniu obcych struktur do domeny.",[10,1191,1192],{},"Dobrze zaprojektowana komunikacja sprawia, że moduł można uruchomić w tym samym procesie, przenieść do osobnego procesu albo zastąpić zewnętrzną usługą bez przebudowy całego systemu. Takie podejście ułatwi w przyszłości migrację modułowego monolitu do architektury mikroserwisów.",[10,1194,1195],{},"Nie oznacza to, że wydzielenie mikroserwisu jest całkowicie bezkosztowe. Zmieniają się między innymi charakter transakcji, opóźnienia, obsługa błędów i wymagania dotyczące obserwowalności. Jawne kontrakty i brak współdzielonego stanu powodują jednak, że jest to kontrolowana zmiana infrastrukturalna, a nie konieczność rozcinania silnie sprzężonego monolitu.",[34,1197,1198,1211,1226],{},[10,1199,1200,1203,1205,1206,1210],{},[50,1201,1202],{},"Czy wiesz, że?",[234,1204],{},"\nOdpowiednie nazewnictwo agregatów, encji, procesów i operacji jest częścią ",[1207,1208,1209],"em",{},"Ubiquitous Language"," — wspólnego języka używanego przez ekspertów domenowych i zespół techniczny.",[10,1212,1213,1214,1217,1218,1221,1222,1225],{},"To samo pojęcie może w różnych modułach pełnić zupełnie inną rolę, dlatego nie zawsze powinno mieć tę samą nazwę. Osoba korzystająca z systemu może być na przykład ",[50,1215,1216],{},"użytkownikiem"," w module tożsamości, ",[50,1219,1220],{},"klientem"," w module sprzedaży i ",[50,1223,1224],{},"autorem"," komentarza w module treści.",[10,1227,1228],{},"Choć technicznie może to być ta sama osoba, w każdej z tych domen obowiązują inne reguły, dane i zachowania.",[17,1230,1232],{"id":1231},"implementacja-kod-który-da-się-utrzymać","Implementacja: kod, który da się utrzymać",[10,1234,1235],{},"Na poziomie implementacji trzymam się kilku zasad, które sprawdzają się niezależnie od projektu.",[260,1237,1238,1241,1244],{},[263,1239,1240],{},"Logika domenowa nie zależy od frameworka ani od bazy danych.",[263,1242,1243],{},"Efekty uboczne (I\u002FO, sieć, czas) są odsunięte na brzeg systemu.",[263,1245,1246],{},"Testy pokrywają zachowania, nie szczegóły implementacji.",[10,1248,1249],{},"To nie jest dogmatyczne Clean Architecture dla samej czystości. Chodzi o to, żeby zmiana w jednym miejscu nie wymagała ostrożnego przechodzenia przez cały system.",[34,1251,1252],{},[10,1253,1254,1255,1258],{},"Pamiętajmy, że ",[50,1256,1257],{},"Clean Architecture"," to nie struktura folderów, CQRS, wzorzec mediatora czy ładnie ponazywane klasy. Czysta architekrura to realne zależności między warstwami systemu.",[17,1260,1262],{"id":1261},"produkcja-zaczyna-się-przed-pierwszym-wdrożeniem","Produkcja zaczyna się przed pierwszym wdrożeniem",[10,1264,1265],{},"Najczęstszy błąd to traktowanie wdrożenia jako ostatniego kroku. W praktyce o produkcji trzeba myśleć od początku: jak aplikacja się konfiguruje, jak loguje, jak sygnalizuje swój stan. Na podstawie własnego doświadczenia mogę powiedzieć, że przygotowanie do wdrożenia produkcyjnego zaczyna się w momencie, gdy podejmowane są pierwsze decyzje architektoniczne, a sam proces wdrożenia jest złożony i wielowarstwowy.",[55,1267,1271],{"className":1268,"code":1269,"language":1270,"meta":60,"style":60},"language-yaml shiki shiki-themes one-dark-pro one-dark-pro","# Zdrowie aplikacji to część kontraktu z infrastrukturą, nie dodatek.\nhealthcheck:\n  test: [\"CMD\", \"curl\", \"-f\", \"http:\u002F\u002Flocalhost:8080\u002Fhealth\u002Fready\"]\n  interval: 10s\n  timeout: 3s\n  retries: 3\n","yaml",[62,1272,1273,1278,1286,1316,1327,1337],{"__ignoreMap":60},[65,1274,1275],{"class":67,"line":68},[65,1276,1277],{"class":71},"# Zdrowie aplikacji to część kontraktu z infrastrukturą, nie dodatek.\n",[65,1279,1280,1283],{"class":67,"line":75},[65,1281,1282],{"class":579},"healthcheck",[65,1284,1285],{"class":98},":\n",[65,1287,1288,1291,1294,1298,1300,1303,1305,1308,1310,1313],{"class":67,"line":81},[65,1289,1290],{"class":579},"  test",[65,1292,1293],{"class":98},": [",[65,1295,1297],{"class":1296},"s7bFL","\"CMD\"",[65,1299,787],{"class":98},[65,1301,1302],{"class":1296},"\"curl\"",[65,1304,787],{"class":98},[65,1306,1307],{"class":1296},"\"-f\"",[65,1309,787],{"class":98},[65,1311,1312],{"class":1296},"\"http:\u002F\u002Flocalhost:8080\u002Fhealth\u002Fready\"",[65,1314,1315],{"class":98},"]\n",[65,1317,1318,1321,1324],{"class":67,"line":95},[65,1319,1320],{"class":579},"  interval",[65,1322,1323],{"class":98},": ",[65,1325,1326],{"class":1296},"10s\n",[65,1328,1329,1332,1334],{"class":67,"line":102},[65,1330,1331],{"class":579},"  timeout",[65,1333,1323],{"class":98},[65,1335,1336],{"class":1296},"3s\n",[65,1338,1339,1342,1344],{"class":67,"line":108},[65,1340,1341],{"class":579},"  retries",[65,1343,1323],{"class":98},[65,1345,1347],{"class":1346},"sDrLb","3\n",[10,1349,1350,1352],{},[234,1351],{},"\nZanim system trafi na produkcję, powinien posiadać bezwzględne minimum:",[260,1354,1355,1362,1368],{},[263,1356,1357,1358,1361],{},"końcówkę zdrowia, czyli tzw. \"",[50,1359,1360],{},"health check","\",",[263,1363,1364,1367],{},[50,1365,1366],{},"ustrukturyzowane logi",", zawierające co najmniej logowanie krytycznych procesów, błędów, wyjątków,",[263,1369,1370,1371,1374],{},"podstawowe ",[50,1372,1373],{},"metryki"," oraz jasną procedurę wdrożenia i wycofania zmiany.",[10,1376,1377],{},"Te dwa ostatnie często nazywane są \"obserwowalnością\". Całość to minimum, które zamienia \"apka wystartowała\" w coś więcej: „ta aplikacja działa i można nią zarządzać\".",[17,1379,1381],{"id":1380},"stabilizacja-i-przekazanie","Stabilizacja i przekazanie",[10,1383,1384],{},"Po wdrożeniu przychodzi etap, który łatwo pominąć: obserwacja systemu pod realnym ruchem, dostrojenie alertów i sprawdzenie, że backupy naprawdę da się odtworzyć. Backup, którego nigdy nie przywrócono, jest tylko nadzieją zapisaną na dysku.",[10,1386,1387],{},"Na końcu zostaje dokumentacja i przekazanie wiedzy. System, który rozumie tylko jego autor, jest ryzykiem operacyjnym — niezależnie od tego, jak dobrze został napisany.",[17,1389,1391],{"id":1390},"podsumowanie","Podsumowanie",[10,1393,1394],{},"Droga od pomysłu do produkcji nie jest liniowa, ale ma sensowną kolejność: najpierw ograniczenia, potem granice, potem implementacja, a przez cały czas — myślenie o tym, jak to będzie działać i jak będzie zarządzane. To podejście nie gwarantuje, że nic się nie zepsuje. Gwarantuje, że gdy się zepsuje, będzie wiadomo co, dlaczego i jak przywrócić do normy.",[1396,1397,1398],"style",{},"html pre.shiki code .sIbxt, html code.shiki .sIbxt{--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic}html pre.shiki code .sCyHM, html code.shiki .sCyHM{--shiki-default:#C678DD;--shiki-dark:#C678DD}html pre.shiki code .sZsuq, html code.shiki .sZsuq{--shiki-default:#E5C07B;--shiki-dark:#E5C07B}html pre.shiki code .shasQ, html code.shiki .shasQ{--shiki-default:#ABB2BF;--shiki-dark:#ABB2BF}html pre.shiki code .sw6ri, html code.shiki .sw6ri{--shiki-default:#61AFEF;--shiki-dark:#61AFEF}html pre.shiki code .seqx8, html code.shiki .seqx8{--shiki-default:#56B6C2;--shiki-dark:#56B6C2}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 .sh-OU, html code.shiki .sh-OU{--shiki-default:#E06C75;--shiki-dark:#E06C75}html pre.shiki code .s7bFL, html code.shiki .s7bFL{--shiki-default:#98C379;--shiki-dark:#98C379}html pre.shiki code .sDrLb, html code.shiki .sDrLb{--shiki-default:#D19A66;--shiki-dark:#D19A66}",{"title":60,"searchDepth":81,"depth":81,"links":1400},[1401,1402,1411,1412,1413,1414],{"id":19,"depth":75,"text":20},{"id":41,"depth":75,"text":42,"children":1403},[1404],{"id":248,"depth":81,"text":249,"children":1405},[1406,1407,1408,1409,1410],{"id":275,"depth":95,"text":276},{"id":366,"depth":95,"text":367},{"id":499,"depth":95,"text":500},{"id":1100,"depth":95,"text":1101},{"id":1158,"depth":95,"text":1159},{"id":1231,"depth":75,"text":1232},{"id":1261,"depth":75,"text":1262},{"id":1380,"depth":75,"text":1381},{"id":1390,"depth":75,"text":1391},"https:\u002F\u002Fjetstreamaviation.pl\u002Fblog\u002Farchitecture-from-idea-to-production","Architecture","2026-07-13","Jak prowadzić projekt od pierwszej decyzji architektonicznej po stabilne, monitorowane wdrożenie — bez przeskakiwania od razu do kodu.","md","\u002Fimages\u002Fblog\u002Farchitecture-to-production.webp",{},{"title":1423,"description":1424,"image":1425,"type":1426},"Od pomysłu do produkcji","Architektura, która nie zawiedzie pod obciążeniem.","\u002Fimages\u002Fog\u002Farchitecture-to-production.jpg","article","\u002Fblog\u002Farchitecture-from-idea-to-production",{"title":1429,"text":1430,"ctaLabel":1431,"url":1432,"accent":1433,"bg":1434,"fg":1435},"Twojemu systemowi brakuje powyższych wzorców?","Skalowalność Twojego systemu zależy od zastosowanych wzorców. Być może procesy, które zachodzą w Twojej aplikacji to wąskie gardło w Twoim biznesie i wymaga ponownego zdefiniowania oraz reimplementacji.","Umów konsultację","\u002Fkontakt","#10BCDD","#0b1f2a","#eaf1f7",{"title":1437,"description":1438,"keywords":1439,"robots":1444},"Od pomysłu do produkcji — architektura systemów w praktyce","Praktyczny przewodnik: od decyzji architektonicznych, przez implementację w .NET, po wdrożenie produkcyjne z monitoringiem i procedurami.",[1440,1441,1442,1443,1257],"architektura systemów",".NET","wdrożenie produkcyjne","DevOps","index,follow","blog\u002Farchitecture-from-idea-to-production",[1416,1441,1443],{"card":1448},"summary_large_image","dtpIaJ8BOvvSU3U-SuIvZocV8NcQPvM9IRM6iujd4bU",{"id":1451,"title":1452,"actions":1453,"areas":1453,"aviationList":1453,"blogChrome":1454,"body":1469,"canonical":1453,"cards":1453,"contact":1453,"cta":1480,"description":1486,"extension":1419,"eyebrow":1453,"hero":1453,"heroImage":1453,"list":1453,"meta":1487,"navigation":157,"og":1453,"partners":1453,"path":1488,"perspective":1453,"portfolioChrome":1489,"process":1453,"sectionHeader":1453,"sections":1453,"seo":1500,"serviceLabels":1453,"services":1453,"stem":1502,"twitter":1453,"__hash__":1503},"pages\u002Fpages\u002Fglobals.md","Globalne teksty UI",null,{"readingTimeSuffix":1455,"tocTitle":1456,"cta":1457,"related":1461,"tag":1464},"min czytania","Spis treści",{"eyebrow":1458,"title":1459,"text":1460},"Współpraca","Ten temat dotyczy Twojego systemu?","Jeśli mierzysz się z podobnym problemem w praktyce — napisz. Spojrzę na konkret i podpowiem kierunek.",{"eyebrow":1462,"title":1463},"Czytaj dalej","Powiązane wpisy",{"eyebrow":1465,"bodyPrefix":1466,"backLabel":1467,"emptyText":1468},"Tag","Wpisy powiązane z tematem","Wróć do wszystkich wpisów →","Brak wpisów dla tego tagu.",{"type":7,"value":1470,"toc":1478},[1471],[10,1472,1473,1474,1477],{},"Ten dokument nie ma własnej strony (",[62,1475,1476],{},"noindex","). Służy wyłącznie jako źródło współdzielonych tekstów interfejsu, które pojawiają się na wielu stronach naraz — dzięki temu można je edytować w jednym miejscu, bez wchodzenia w kod komponentów.",{"title":60,"searchDepth":81,"depth":81,"links":1479},[],{"eyebrow":1458,"title":1481,"text":1482,"primaryLabel":1483,"primaryTo":1432,"secondaryLabel":1484,"secondaryTo":1485},"Porozmawiajmy o projekcie","Opisz problem albo cel — odezwę się z konkretną propozycją następnego kroku: rozmowy, audytu, zakresu prac lub ofertą.","Napisz do mnie","Zobacz usługi IT","\u002Fuslugi-it","Współdzielone etykiety i domyślne treści renderowane na wielu stronach (CtaBlock, strony szczegółów blog\u002Fportfolio, tagi). Edytowane tutaj, bez zmian w kodzie.",{},"\u002Fpages\u002Fglobals",{"caseStudyEyebrow":1490,"meta":1491,"stackTitle":1495,"areasTitle":1496,"related":1497},"Case study",{"type":1492,"client":1493,"date":1494},"Typ","Klient","Data","Stack","Obszary",{"eyebrow":1498,"title":1499},"Więcej","Powiązane projekty",{"robots":1501,"title":1452,"description":1486},"noindex,nofollow","pages\u002Fglobals","_s0SDmcqMxpaZUVz5yYm0sZItpGh6_JwnFv3nOsfADs",[1505,2539],{"id":4,"title":5,"body":1506,"canonical":1415,"category":1416,"date":1417,"description":1418,"extension":1419,"featured":157,"image":1420,"meta":2532,"navigation":157,"og":2533,"path":1427,"readingTime":698,"recommendation":2534,"seo":2535,"stem":1445,"tags":2537,"twitter":2538,"updated":1417,"__hash__":1449},{"type":7,"value":1507,"toc":2516},[1508,1510,1512,1514,1518,1520,1524,1526,1528,1532,1648,1652,1658,1660,1662,1664,1666,1674,1676,1678,1740,1744,1746,1748,1750,1752,1754,1794,1798,1810,1812,1814,1824,1826,1828,1830,1844,1846,1848,1850,1852,1870,1872,1876,2306,2310,2312,2314,2316,2318,2320,2328,2330,2340,2342,2352,2354,2356,2358,2370,2372,2376,2378,2380,2400,2402,2404,2412,2414,2420,2422,2424,2484,2488,2502,2504,2506,2508,2510,2512,2514],[10,1509,12],{},[10,1511,15],{},[17,1513,20],{"id":19},[10,1515,23,1516,29],{},[25,1517,28],{"href":27},[10,1519,32],{},[34,1521,1522],{},[10,1523,38],{},[17,1525,42],{"id":41},[10,1527,45],{},[10,1529,48,1530,53],{},[50,1531,52],{},[55,1533,1534],{"className":57,"code":58,"language":59,"meta":60,"style":60},[62,1535,1536,1540,1544,1552,1556,1560,1574,1582,1590,1594,1598,1620,1634,1644],{"__ignoreMap":60},[65,1537,1538],{"class":67,"line":68},[65,1539,72],{"class":71},[65,1541,1542],{"class":67,"line":75},[65,1543,78],{"class":71},[65,1545,1546,1548,1550],{"class":67,"line":81},[65,1547,85],{"class":84},[65,1549,88],{"class":84},[65,1551,92],{"class":91},[65,1553,1554],{"class":67,"line":95},[65,1555,99],{"class":98},[65,1557,1558],{"class":67,"line":102},[65,1559,105],{"class":71},[65,1561,1562,1564,1566,1568,1570,1572],{"class":67,"line":108},[65,1563,111],{"class":91},[65,1565,114],{"class":98},[65,1567,117],{"class":91},[65,1569,120],{"class":98},[65,1571,124],{"class":123},[65,1573,127],{"class":98},[65,1575,1576,1578,1580],{"class":67,"line":130},[65,1577,133],{"class":91},[65,1579,136],{"class":91},[65,1581,139],{"class":98},[65,1583,1584,1586,1588],{"class":67,"line":142},[65,1585,145],{"class":91},[65,1587,148],{"class":91},[65,1589,151],{"class":98},[65,1591,1592],{"class":67,"line":154},[65,1593,158],{"emptyLinePlaceholder":157},[65,1595,1596],{"class":67,"line":161},[65,1597,164],{"class":71},[65,1599,1600,1602,1604,1606,1608,1610,1612,1614,1616,1618],{"class":67,"line":167},[65,1601,111],{"class":91},[65,1603,114],{"class":98},[65,1605,117],{"class":91},[65,1607,114],{"class":98},[65,1609,178],{"class":91},[65,1611,181],{"class":98},[65,1613,184],{"class":123},[65,1615,114],{"class":98},[65,1617,189],{"class":91},[65,1619,192],{"class":98},[65,1621,1622,1624,1626,1628,1630,1632],{"class":67,"line":195},[65,1623,198],{"class":91},[65,1625,114],{"class":98},[65,1627,189],{"class":91},[65,1629,120],{"class":98},[65,1631,207],{"class":91},[65,1633,139],{"class":98},[65,1635,1636,1638,1640,1642],{"class":67,"line":212},[65,1637,145],{"class":91},[65,1639,217],{"class":91},[65,1641,221],{"class":220},[65,1643,224],{"class":98},[65,1645,1646],{"class":67,"line":227},[65,1647,230],{"class":98},[10,1649,1650,236],{},[234,1651],{},[34,1653,1654],{},[10,1655,241,1656,244],{},[234,1657],{},[246,1659,249],{"id":248},[10,1661,252],{},[10,1663,255],{},[10,1665,258],{},[260,1667,1668,1670,1672],{},[263,1669,265],{},[263,1671,268],{},[263,1673,271],{},[273,1675,276],{"id":275},[10,1677,279],{},[55,1679,1680],{"className":57,"code":282,"language":59,"meta":60,"style":60},[62,1681,1682,1686,1694,1698,1702,1706,1720,1728,1736],{"__ignoreMap":60},[65,1683,1684],{"class":67,"line":68},[65,1685,289],{"class":71},[65,1687,1688,1690,1692],{"class":67,"line":75},[65,1689,85],{"class":84},[65,1691,88],{"class":84},[65,1693,298],{"class":91},[65,1695,1696],{"class":67,"line":81},[65,1697,303],{"class":98},[65,1699,1700],{"class":67,"line":95},[65,1701,308],{"class":71},[65,1703,1704],{"class":67,"line":102},[65,1705,313],{"class":71},[65,1707,1708,1710,1712,1714,1716,1718],{"class":67,"line":108},[65,1709,111],{"class":91},[65,1711,114],{"class":98},[65,1713,322],{"class":91},[65,1715,120],{"class":98},[65,1717,327],{"class":123},[65,1719,127],{"class":98},[65,1721,1722,1724,1726],{"class":67,"line":130},[65,1723,334],{"class":91},[65,1725,337],{"class":91},[65,1727,139],{"class":98},[65,1729,1730,1732,1734],{"class":67,"line":142},[65,1731,145],{"class":91},[65,1733,217],{"class":91},[65,1735,348],{"class":98},[65,1737,1738],{"class":67,"line":154},[65,1739,230],{"class":98},[10,1741,1742,357],{},[234,1743],{},[10,1745,360],{},[10,1747,363],{},[273,1749,367],{"id":366},[10,1751,370],{},[10,1753,373],{},[55,1755,1756],{"className":57,"code":376,"language":59,"meta":60,"style":60},[62,1757,1758,1770,1778,1786],{"__ignoreMap":60},[65,1759,1760,1762,1764,1766,1768],{"class":67,"line":68},[65,1761,85],{"class":84},[65,1763,385],{"class":84},[65,1765,388],{"class":84},[65,1767,391],{"class":91},[65,1769,127],{"class":98},[65,1771,1772,1774,1776],{"class":67,"line":75},[65,1773,398],{"class":91},[65,1775,401],{"class":91},[65,1777,139],{"class":98},[65,1779,1780,1782,1784],{"class":67,"line":81},[65,1781,398],{"class":91},[65,1783,410],{"class":91},[65,1785,139],{"class":98},[65,1787,1788,1790,1792],{"class":67,"line":95},[65,1789,417],{"class":91},[65,1791,420],{"class":91},[65,1793,151],{"class":98},[10,1795,1796,427],{},[234,1797],{},[260,1799,1800,1802,1804,1806,1808],{},[263,1801,432],{},[263,1803,435],{},[263,1805,438],{},[263,1807,441],{},[263,1809,444],{},[10,1811,447],{},[10,1813,450],{},[260,1815,1816,1820],{},[263,1817,1818,458],{},[50,1819,457],{},[263,1821,1822,464],{},[50,1823,463],{},[10,1825,467],{},[10,1827,470],{},[10,1829,473],{},[260,1831,1832,1836,1840],{},[263,1833,1834,481],{},[50,1835,480],{},[263,1837,1838,487],{},[50,1839,486],{},[263,1841,1842,493],{},[50,1843,492],{},[10,1845,496],{},[273,1847,500],{"id":499},[10,1849,503],{},[10,1851,506],{},[508,1853,1854,1856,1858,1860,1862,1864,1866,1868],{},[263,1855,512],{},[263,1857,515],{},[263,1859,518],{},[263,1861,521],{},[263,1863,524],{},[263,1865,527],{},[263,1867,530],{},[263,1869,533],{},[10,1871,536],{},[34,1873,1874],{},[10,1875,541],{},[55,1877,1878],{"className":57,"code":544,"language":59,"meta":60,"style":60},[62,1879,1880,1892,1896,1908,1920,1932,1936,1944,1952,1960,1968,1972,1982,1992,2002,2006,2010,2028,2036,2044,2048,2074,2078,2092,2104,2108,2126,2146,2152,2156,2170,2174,2186,2200,2210,2216,2220,2232,2236,2240,2252,2266,2276,2282,2286,2298,2302],{"__ignoreMap":60},[65,1881,1882,1884,1886,1888,1890],{"class":67,"line":68},[65,1883,85],{"class":84},[65,1885,385],{"class":84},[65,1887,555],{"class":84},[65,1889,558],{"class":91},[65,1891,561],{"class":98},[65,1893,1894],{"class":67,"line":75},[65,1895,303],{"class":98},[65,1897,1898,1900,1902,1904,1906],{"class":67,"line":81},[65,1899,570],{"class":84},[65,1901,573],{"class":84},[65,1903,576],{"class":91},[65,1905,580],{"class":579},[65,1907,583],{"class":98},[65,1909,1910,1912,1914,1916,1918],{"class":67,"line":95},[65,1911,570],{"class":84},[65,1913,573],{"class":84},[65,1915,592],{"class":91},[65,1917,595],{"class":579},[65,1919,583],{"class":98},[65,1921,1922,1924,1926,1928,1930],{"class":67,"line":102},[65,1923,570],{"class":84},[65,1925,573],{"class":84},[65,1927,606],{"class":91},[65,1929,609],{"class":579},[65,1931,583],{"class":98},[65,1933,1934],{"class":67,"line":108},[65,1935,616],{"class":98},[65,1937,1938,1940,1942],{"class":67,"line":130},[65,1939,621],{"class":84},[65,1941,558],{"class":123},[65,1943,127],{"class":98},[65,1945,1946,1948,1950],{"class":67,"line":142},[65,1947,630],{"class":91},[65,1949,633],{"class":91},[65,1951,636],{"class":98},[65,1953,1954,1956,1958],{"class":67,"line":154},[65,1955,641],{"class":91},[65,1957,644],{"class":91},[65,1959,636],{"class":98},[65,1961,1962,1964,1966],{"class":67,"line":161},[65,1963,651],{"class":91},[65,1965,654],{"class":91},[65,1967,657],{"class":98},[65,1969,1970],{"class":67,"line":167},[65,1971,662],{"class":98},[65,1973,1974,1976,1978,1980],{"class":67,"line":195},[65,1975,667],{"class":579},[65,1977,221],{"class":220},[65,1979,633],{"class":579},[65,1981,583],{"class":98},[65,1983,1984,1986,1988,1990],{"class":67,"line":212},[65,1985,678],{"class":579},[65,1987,221],{"class":220},[65,1989,644],{"class":579},[65,1991,583],{"class":98},[65,1993,1994,1996,1998,2000],{"class":67,"line":227},[65,1995,689],{"class":579},[65,1997,221],{"class":220},[65,1999,654],{"class":579},[65,2001,583],{"class":98},[65,2003,2004],{"class":67,"line":698},[65,2005,701],{"class":98},[65,2007,2008],{"class":67,"line":704},[65,2009,158],{"emptyLinePlaceholder":157},[65,2011,2012,2014,2016,2018,2020,2022,2024,2026],{"class":67,"line":709},[65,2013,621],{"class":84},[65,2015,714],{"class":84},[65,2017,717],{"class":91},[65,2019,114],{"class":98},[65,2021,322],{"class":91},[65,2023,120],{"class":98},[65,2025,726],{"class":123},[65,2027,127],{"class":98},[65,2029,2030,2032,2034],{"class":67,"line":731},[65,2031,734],{"class":91},[65,2033,136],{"class":91},[65,2035,139],{"class":98},[65,2037,2038,2040,2042],{"class":67,"line":741},[65,2039,744],{"class":91},[65,2041,217],{"class":91},[65,2043,749],{"class":98},[65,2045,2046],{"class":67,"line":752},[65,2047,755],{"class":98},[65,2049,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2072],{"class":67,"line":758},[65,2051,761],{"class":84},[65,2053,764],{"class":579},[65,2055,221],{"class":220},[65,2057,769],{"class":98},[65,2059,772],{"class":91},[65,2061,775],{"class":98},[65,2063,778],{"class":123},[65,2065,781],{"class":98},[65,2067,784],{"class":579},[65,2069,787],{"class":98},[65,2071,790],{"class":579},[65,2073,151],{"class":98},[65,2075,2076],{"class":67,"line":795},[65,2077,158],{"emptyLinePlaceholder":157},[65,2079,2080,2082,2084,2086,2088,2090],{"class":67,"line":800},[65,2081,803],{"class":84},[65,2083,806],{"class":98},[65,2085,809],{"class":91},[65,2087,775],{"class":98},[65,2089,814],{"class":91},[65,2091,749],{"class":98},[65,2093,2094,2096,2098,2100,2102],{"class":67,"line":819},[65,2095,822],{"class":84},[65,2097,764],{"class":91},[65,2099,775],{"class":98},[65,2101,829],{"class":91},[65,2103,832],{"class":98},[65,2105,2106],{"class":67,"line":835},[65,2107,838],{"class":98},[65,2109,2110,2112,2114,2116,2118,2120,2122,2124],{"class":67,"line":841},[65,2111,761],{"class":84},[65,2113,846],{"class":579},[65,2115,221],{"class":220},[65,2117,769],{"class":98},[65,2119,853],{"class":91},[65,2121,775],{"class":98},[65,2123,858],{"class":123},[65,2125,127],{"class":98},[65,2127,2128,2130,2132,2134,2136,2138,2140,2142,2144],{"class":67,"line":863},[65,2129,866],{"class":98},[65,2131,869],{"class":91},[65,2133,781],{"class":98},[65,2135,809],{"class":91},[65,2137,775],{"class":98},[65,2139,878],{"class":91},[65,2141,775],{"class":98},[65,2143,883],{"class":91},[65,2145,886],{"class":98},[65,2147,2148,2150],{"class":67,"line":889},[65,2149,892],{"class":579},[65,2151,151],{"class":98},[65,2153,2154],{"class":67,"line":897},[65,2155,838],{"class":98},[65,2157,2158,2160,2162,2164,2166,2168],{"class":67,"line":902},[65,2159,803],{"class":84},[65,2161,806],{"class":98},[65,2163,909],{"class":91},[65,2165,775],{"class":98},[65,2167,814],{"class":91},[65,2169,749],{"class":98},[65,2171,2172],{"class":67,"line":918},[65,2173,921],{"class":98},[65,2175,2176,2178,2180,2182,2184],{"class":67,"line":924},[65,2177,927],{"class":98},[65,2179,772],{"class":91},[65,2181,775],{"class":98},[65,2183,934],{"class":123},[65,2185,127],{"class":98},[65,2187,2188,2190,2192,2194,2196,2198],{"class":67,"line":939},[65,2189,942],{"class":91},[65,2191,775],{"class":98},[65,2193,878],{"class":91},[65,2195,775],{"class":98},[65,2197,883],{"class":91},[65,2199,139],{"class":98},[65,2201,2202,2204,2206,2208],{"class":67,"line":955},[65,2203,958],{"class":91},[65,2205,775],{"class":98},[65,2207,829],{"class":91},[65,2209,139],{"class":98},[65,2211,2212,2214],{"class":67,"line":967},[65,2213,970],{"class":579},[65,2215,348],{"class":98},[65,2217,2218],{"class":67,"line":975},[65,2219,978],{"class":98},[65,2221,2222,2224,2226,2228,2230],{"class":67,"line":981},[65,2223,822],{"class":84},[65,2225,846],{"class":91},[65,2227,775],{"class":98},[65,2229,829],{"class":91},[65,2231,832],{"class":98},[65,2233,2234],{"class":67,"line":994},[65,2235,997],{"class":98},[65,2237,2238],{"class":67,"line":1000},[65,2239,158],{"emptyLinePlaceholder":157},[65,2241,2242,2244,2246,2248,2250],{"class":67,"line":1005},[65,2243,1008],{"class":98},[65,2245,1011],{"class":91},[65,2247,775],{"class":98},[65,2249,1016],{"class":123},[65,2251,127],{"class":98},[65,2253,2254,2256,2258,2260,2262,2264],{"class":67,"line":1021},[65,2255,1024],{"class":91},[65,2257,775],{"class":98},[65,2259,878],{"class":91},[65,2261,775],{"class":98},[65,2263,883],{"class":91},[65,2265,139],{"class":98},[65,2267,2268,2270,2272,2274],{"class":67,"line":1037},[65,2269,1040],{"class":91},[65,2271,775],{"class":98},[65,2273,1045],{"class":91},[65,2275,139],{"class":98},[65,2277,2278,2280],{"class":67,"line":1050},[65,2279,892],{"class":579},[65,2281,151],{"class":98},[65,2283,2284],{"class":67,"line":1057},[65,2285,158],{"emptyLinePlaceholder":157},[65,2287,2288,2290,2292,2294,2296],{"class":67,"line":1062},[65,2289,1065],{"class":84},[65,2291,1068],{"class":91},[65,2293,775],{"class":98},[65,2295,1073],{"class":123},[65,2297,1076],{"class":98},[65,2299,2300],{"class":67,"line":1079},[65,2301,701],{"class":98},[65,2303,2304],{"class":67,"line":1084},[65,2305,230],{"class":98},[10,2307,2308,1091],{},[234,2309],{},[10,2311,1094],{},[10,2313,1097],{},[273,2315,1101],{"id":1100},[10,2317,1104],{},[10,2319,1107],{},[260,2321,2322,2324,2326],{},[263,2323,1112],{},[263,2325,1115],{},[263,2327,1118],{},[10,2329,1121],{},[260,2331,2332,2334,2336,2338],{},[263,2333,1126],{},[263,2335,1129],{},[263,2337,1132],{},[263,2339,1135],{},[10,2341,1138],{},[260,2343,2344,2346,2348,2350],{},[263,2345,1143],{},[263,2347,1146],{},[263,2349,1149],{},[263,2351,1152],{},[10,2353,1155],{},[273,2355,1159],{"id":1158},[10,2357,1162],{},[260,2359,2360,2362,2364,2366,2368],{},[263,2361,1167],{},[263,2363,1170],{},[263,2365,1173],{},[263,2367,1176],{},[263,2369,1179],{},[10,2371,1182],{},[10,2373,1185,2374,1189],{},[50,2375,1188],{},[10,2377,1192],{},[10,2379,1195],{},[34,2381,2382,2390,2398],{},[10,2383,2384,2386,1205,2388,1210],{},[50,2385,1202],{},[234,2387],{},[1207,2389,1209],{},[10,2391,1213,2392,1217,2394,1221,2396,1225],{},[50,2393,1216],{},[50,2395,1220],{},[50,2397,1224],{},[10,2399,1228],{},[17,2401,1232],{"id":1231},[10,2403,1235],{},[260,2405,2406,2408,2410],{},[263,2407,1240],{},[263,2409,1243],{},[263,2411,1246],{},[10,2413,1249],{},[34,2415,2416],{},[10,2417,1254,2418,1258],{},[50,2419,1257],{},[17,2421,1262],{"id":1261},[10,2423,1265],{},[55,2425,2426],{"className":1268,"code":1269,"language":1270,"meta":60,"style":60},[62,2427,2428,2432,2438,2460,2468,2476],{"__ignoreMap":60},[65,2429,2430],{"class":67,"line":68},[65,2431,1277],{"class":71},[65,2433,2434,2436],{"class":67,"line":75},[65,2435,1282],{"class":579},[65,2437,1285],{"class":98},[65,2439,2440,2442,2444,2446,2448,2450,2452,2454,2456,2458],{"class":67,"line":81},[65,2441,1290],{"class":579},[65,2443,1293],{"class":98},[65,2445,1297],{"class":1296},[65,2447,787],{"class":98},[65,2449,1302],{"class":1296},[65,2451,787],{"class":98},[65,2453,1307],{"class":1296},[65,2455,787],{"class":98},[65,2457,1312],{"class":1296},[65,2459,1315],{"class":98},[65,2461,2462,2464,2466],{"class":67,"line":95},[65,2463,1320],{"class":579},[65,2465,1323],{"class":98},[65,2467,1326],{"class":1296},[65,2469,2470,2472,2474],{"class":67,"line":102},[65,2471,1331],{"class":579},[65,2473,1323],{"class":98},[65,2475,1336],{"class":1296},[65,2477,2478,2480,2482],{"class":67,"line":108},[65,2479,1341],{"class":579},[65,2481,1323],{"class":98},[65,2483,1347],{"class":1346},[10,2485,2486,1352],{},[234,2487],{},[260,2489,2490,2494,2498],{},[263,2491,1357,2492,1361],{},[50,2493,1360],{},[263,2495,2496,1367],{},[50,2497,1366],{},[263,2499,1370,2500,1374],{},[50,2501,1373],{},[10,2503,1377],{},[17,2505,1381],{"id":1380},[10,2507,1384],{},[10,2509,1387],{},[17,2511,1391],{"id":1390},[10,2513,1394],{},[1396,2515,1398],{},{"title":60,"searchDepth":81,"depth":81,"links":2517},[2518,2519,2528,2529,2530,2531],{"id":19,"depth":75,"text":20},{"id":41,"depth":75,"text":42,"children":2520},[2521],{"id":248,"depth":81,"text":249,"children":2522},[2523,2524,2525,2526,2527],{"id":275,"depth":95,"text":276},{"id":366,"depth":95,"text":367},{"id":499,"depth":95,"text":500},{"id":1100,"depth":95,"text":1101},{"id":1158,"depth":95,"text":1159},{"id":1231,"depth":75,"text":1232},{"id":1261,"depth":75,"text":1262},{"id":1380,"depth":75,"text":1381},{"id":1390,"depth":75,"text":1391},{},{"title":1423,"description":1424,"image":1425,"type":1426},{"title":1429,"text":1430,"ctaLabel":1431,"url":1432,"accent":1433,"bg":1434,"fg":1435},{"title":1437,"description":1438,"keywords":2536,"robots":1444},[1440,1441,1442,1443,1257],[1416,1441,1443],{"card":1448},{"id":2540,"title":2541,"body":2542,"canonical":2799,"category":1443,"date":1417,"description":2800,"extension":1419,"featured":2801,"image":2802,"meta":2803,"navigation":157,"og":2804,"path":27,"readingTime":142,"recommendation":2808,"seo":2815,"stem":2823,"tags":2824,"twitter":2826,"updated":1453,"__hash__":2827},"blog\u002Fblog\u002Fgitops-on-vps-infrastructure.md","GitOps na VPS: przewidywalne wdrożenia bez wielkiej platformy",{"type":7,"value":2543,"toc":2792},[2544,2551,2555,2558,2561,2565,2568,2689,2694,2698,2701,2712,2764,2769,2774,2778,2781,2784,2786,2789],[10,2545,2546,2547,2550],{},"GitOps kojarzy się z Kubernetesem, operatorami i sporą platformą. To skojarzenie jest mylące. Sednem GitOps nie jest konkretne narzędzie, tylko zasada: ",[50,2548,2549],{},"stan systemu jest opisany w repozytorium, a wdrożenie to doprowadzenie rzeczywistości do zgodności z tym opisem",". Tę zasadę można zastosować równie dobrze na pojedynczym VPS z Dockerem.",[17,2552,2554],{"id":2553},"problem-który-gitops-rozwiązuje","Problem, który GitOps rozwiązuje",[10,2556,2557],{},"Typowy VPS po roku eksploatacji to przysłowiowy \"gorący kartofel\". Ktoś kiedyś wgrał obraz ręcznie, ktoś zmienił konfigurację nginx po SSH, a zmienne środowiskowe żyją w pliku, którego nie ma w żadnym repozytorium. Odtworzenie takiego serwera od zera bywa niemożliwe, bo nikt nie wie, jak dokładnie wygląda jego stan.",[10,2559,2560],{},"GitOps odwraca tę sytuację. Jeśli repozytorium jest źródłem prawdy, serwer przestaje być miejscem, w którym trzyma się wiedzę.",[17,2562,2564],{"id":2563},"minimalny-model","Minimalny model",[10,2566,2567],{},"Do sensownego GitOps na VPS wystarczą trzy elementy: repozytorium z deklaratywnym opisem stanu, rejestr obrazów oraz mechanizm, który synchronizuje stan na serwerze z repozytorium.",[55,2569,2571],{"className":1268,"code":2570,"language":1270,"meta":60,"style":60},"# compose.yaml — deklaratywny opis tego, co ma działać na serwerze\nservices:\n  api:\n    image: registry.example.com\u002Fapp\u002Fapi:${APP_VERSION}\n    restart: unless-stopped\n    env_file: [ .env ]\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http:\u002F\u002Flocalhost:8080\u002Fhealth\u002Fready\"]\n      interval: 10s\n      retries: 3\n    labels:\n      - \"com.centurylinklabs.watchtower.enable=true\"\n",[62,2572,2573,2578,2585,2592,2602,2612,2626,2633,2656,2665,2674,2681],{"__ignoreMap":60},[65,2574,2575],{"class":67,"line":68},[65,2576,2577],{"class":71},"# compose.yaml — deklaratywny opis tego, co ma działać na serwerze\n",[65,2579,2580,2583],{"class":67,"line":75},[65,2581,2582],{"class":579},"services",[65,2584,1285],{"class":98},[65,2586,2587,2590],{"class":67,"line":81},[65,2588,2589],{"class":579},"  api",[65,2591,1285],{"class":98},[65,2593,2594,2597,2599],{"class":67,"line":95},[65,2595,2596],{"class":579},"    image",[65,2598,1323],{"class":98},[65,2600,2601],{"class":1296},"registry.example.com\u002Fapp\u002Fapi:${APP_VERSION}\n",[65,2603,2604,2607,2609],{"class":67,"line":102},[65,2605,2606],{"class":579},"    restart",[65,2608,1323],{"class":98},[65,2610,2611],{"class":1296},"unless-stopped\n",[65,2613,2614,2617,2620,2623],{"class":67,"line":108},[65,2615,2616],{"class":579},"    env_file",[65,2618,2619],{"class":98},": [ ",[65,2621,2622],{"class":1296},".env",[65,2624,2625],{"class":98}," ]\n",[65,2627,2628,2631],{"class":67,"line":130},[65,2629,2630],{"class":579},"    healthcheck",[65,2632,1285],{"class":98},[65,2634,2635,2638,2640,2642,2644,2646,2648,2650,2652,2654],{"class":67,"line":142},[65,2636,2637],{"class":579},"      test",[65,2639,1293],{"class":98},[65,2641,1297],{"class":1296},[65,2643,787],{"class":98},[65,2645,1302],{"class":1296},[65,2647,787],{"class":98},[65,2649,1307],{"class":1296},[65,2651,787],{"class":98},[65,2653,1312],{"class":1296},[65,2655,1315],{"class":98},[65,2657,2658,2661,2663],{"class":67,"line":154},[65,2659,2660],{"class":579},"      interval",[65,2662,1323],{"class":98},[65,2664,1326],{"class":1296},[65,2666,2667,2670,2672],{"class":67,"line":161},[65,2668,2669],{"class":579},"      retries",[65,2671,1323],{"class":98},[65,2673,1347],{"class":1346},[65,2675,2676,2679],{"class":67,"line":167},[65,2677,2678],{"class":579},"    labels",[65,2680,1285],{"class":98},[65,2682,2683,2686],{"class":67,"line":195},[65,2684,2685],{"class":98},"      - ",[65,2687,2688],{"class":1296},"\"com.centurylinklabs.watchtower.enable=true\"\n",[10,2690,2691,2693],{},[234,2692],{},"\nWersja aplikacji nie jest wpisana na sztywno — pochodzi ze zmiennej, którą ustawia pipeline. Może to być konkretna wersja lub np. commit SHA. Dzięki temu wdrożenie sprowadza się do zmiany jednej wartości i synchronizacji np. poprzez wywołanie jednego skryptu na VPS.",[17,2695,2697],{"id":2696},"przepływ-wdrożenia","Przepływ wdrożenia",[10,2699,2700],{},"Proces jest prosty i całkowicie prześledzalny w historii Gita:",[260,2702,2703,2706,2709],{},[263,2704,2705],{},"Pipeline buduje obraz i publikuje go w rejestrze z konkretnym tagiem (np. skrótem commita lub pełnym SHA).",[263,2707,2708],{},"Pipeline zdalnie wyzwala skrypt umieszczony na VPS, wykorzystując do tego ograniczone uprawnienia SSH lub webhook.",[263,2710,2711],{},"Docker na VPS pobiera obraz i wdraża kontener za pomocą np. \"compose\", uwzględniając healthcheck.",[55,2713,2717],{"className":2714,"code":2715,"language":2716,"meta":60,"style":60},"language-bash shiki shiki-themes one-dark-pro one-dark-pro","# Uproszczony krok synchronizacji uruchamiany na serwerze\ngit -C \u002Fsrv\u002Fstack pull --ff-only\ndocker compose -f \u002Fsrv\u002Fstack\u002Fcompose.yaml up -d --remove-orphans\n","bash",[62,2718,2719,2724,2741],{"__ignoreMap":60},[65,2720,2721],{"class":67,"line":68},[65,2722,2723],{"class":71},"# Uproszczony krok synchronizacji uruchamiany na serwerze\n",[65,2725,2726,2729,2732,2735,2738],{"class":67,"line":75},[65,2727,2728],{"class":123},"git",[65,2730,2731],{"class":1346}," -C",[65,2733,2734],{"class":1296}," \u002Fsrv\u002Fstack",[65,2736,2737],{"class":1296}," pull",[65,2739,2740],{"class":1346}," --ff-only\n",[65,2742,2743,2746,2749,2752,2755,2758,2761],{"class":67,"line":81},[65,2744,2745],{"class":123},"docker",[65,2747,2748],{"class":1296}," compose",[65,2750,2751],{"class":1346}," -f",[65,2753,2754],{"class":1296}," \u002Fsrv\u002Fstack\u002Fcompose.yaml",[65,2756,2757],{"class":1296}," up",[65,2759,2760],{"class":1346}," -d",[65,2762,2763],{"class":1346}," --remove-orphans\n",[10,2765,2766,2768],{},[234,2767],{},"\nKażde wdrożenie zostawia ślad: wiadomo, kto, co i kiedy zmienił. Wycofanie zmiany to powrót do poprzedniego commita — nie ręczne „przypominanie sobie\", co było wcześniej.",[34,2770,2771],{},[10,2772,2773],{},"Największa zaleta GitOps na VPS nie jest techniczna. To fakt, że proces jest deterministyczny, a zespół ma nad nim kontrolę.",[17,2775,2777],{"id":2776},"czego-to-nie-zastąpi","Czego to nie zastąpi",[10,2779,2780],{},"GitOps na VPS nie daje automatycznego skalowania ani samoregeneracji na poziomie klastra. Jeśli potrzebujesz wielu węzłów, automatycznego rozkładania obciążenia i wdrożeń typu \"rolling\" między replikami, to sygnał, że czas na Kubernetesa.",[10,2782,2783],{},"Dla większości mniejszych i średnich systemów ta granica jest jednak dużo dalej, niż się wydaje. Pojedynczy, dobrze opisany VPS potrafi obsłużyć zaskakująco dużo — pod warunkiem, że jego stan nie jest rozmyty w głowach wielu osób.",[17,2785,1391],{"id":1390},[10,2787,2788],{},"GitOps to dyscyplina, nie platforma. Wdrożona nawet w minimalnej formie na VPS, zamienia serwer z kruchego, ręcznie utrzymywanego bytu w coś przewidywalnego i odtwarzalnego. A przewidywalność jest dokładnie tym, czego oczekujemy od środowiska produkcyjnego.",[1396,2790,2791],{},"html pre.shiki code .sIbxt, html code.shiki .sIbxt{--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic}html pre.shiki code .sh-OU, html code.shiki .sh-OU{--shiki-default:#E06C75;--shiki-dark:#E06C75}html pre.shiki code .shasQ, html code.shiki .shasQ{--shiki-default:#ABB2BF;--shiki-dark:#ABB2BF}html pre.shiki code .s7bFL, html code.shiki .s7bFL{--shiki-default:#98C379;--shiki-dark:#98C379}html pre.shiki code .sDrLb, html code.shiki .sDrLb{--shiki-default:#D19A66;--shiki-dark:#D19A66}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 .sw6ri, html code.shiki .sw6ri{--shiki-default:#61AFEF;--shiki-dark:#61AFEF}",{"title":60,"searchDepth":81,"depth":81,"links":2793},[2794,2795,2796,2797,2798],{"id":2553,"depth":75,"text":2554},{"id":2563,"depth":75,"text":2564},{"id":2696,"depth":75,"text":2697},{"id":2776,"depth":75,"text":2777},{"id":1390,"depth":75,"text":1391},"https:\u002F\u002Fjetstreamaviation.pl\u002Fblog\u002Fgitops-on-vps-infrastructure","Nie potrzebujesz klastra zarządzanego przez zespół lub zewnętrznego dostawcę, żeby mieć dobry i stabilny proces wdrażania. GitOps działa też na pojedynczym VPS — i może mieć większą wartość, niż się wydaje.",false,"\u002Fimages\u002Fblog\u002Fgitops-on-vps.webp",{},{"title":2805,"description":2806,"image":2807,"type":1426},"GitOps na VPS","Przewidywalne wdrożenia bez wielkiej platformy.","\u002Fimages\u002Fog\u002Fgitops-on-vps.jpg",{"logo":2809,"logoAlt":2810,"title":2811,"text":2812,"ctaLabel":2813,"url":2814,"accent":1433,"bg":1434,"fg":1435},"\u002Fimages\u002Flogos\u002Fnsix.svg","NSIX Data Center","Szukasz stabilnego VPS pod GitOps?","NSIX Data Center — prywatna chmura i serwery VPS na polskiej infrastrukturze, z backupem i wsparciem 24\u002F7. Idealne pod proces wdrożenia opisany powyżej.","Sprawdź NSIX","https:\u002F\u002Fnsix.pl\u002F",{"title":2816,"description":2817,"keywords":2818,"robots":1444},"GitOps na VPS — powtarzalne wdrożenia w praktyce","Jak zbudować przewidywalny, opisany w repozytorium proces wdrożeń na zwykłym VPS z Dockerem, bez Kubernetesa i zarządzanej platformy.",[2819,2820,2821,2822,1443],"GitOps","VPS","Docker","CI\u002FCD","blog\u002Fgitops-on-vps-infrastructure",[2819,1443,2821,2825],"Infrastructure",{"card":1448},"-rXvMX51U8DaC8527L3DyAd-kVzKkDjrB-kpaU7gwKw",1784052984653]