187187</ a >
188188</ li >
189189< li class ="md-nav__item ">
190- < a class ="md-nav__link " href ="#deployment-flow ">
190+ < a class ="md-nav__link " href ="#tasks ">
191191< span class ="md-ellipsis ">
192- 🔄 Deployment Flow
192+ 🔄 Tasks
193193 </ span >
194194</ a >
195195</ li >
208208</ a >
209209</ li >
210210< li class ="md-nav__item ">
211- < a class ="md-nav__link " href ="#3-deploying ">
211+ < a class ="md-nav__link " href ="#3-deploying-to-a-remote-server ">
212212< span class ="md-ellipsis ">
213- 🚀 3. Deploying
213+ 🚀 3. Deploying to a Remote Server
214+ </ span >
215+ </ a >
216+ < nav aria-label ="🚀 3. Deploying to a Remote Server " class ="md-nav ">
217+ < ul class ="md-nav__list ">
218+ < li class ="md-nav__item ">
219+ < a class ="md-nav__link " href ="#1-deploy-the-proxy ">
220+ < span class ="md-ellipsis ">
221+ 1. Deploy the Proxy
214222 </ span >
215223</ a >
216224</ li >
217225< li class ="md-nav__item ">
218- < a class ="md-nav__link " href ="#4 -deploy-the -new-app-stack ">
226+ < a class ="md-nav__link " href ="#2 -deploy-a -new-app-version ">
219227< span class ="md-ellipsis ">
220- 🆕 4 . Deploy the New App Stack
228+ 2 . Deploy a new App version
221229 </ span >
222230</ a >
223231</ li >
232+ </ ul >
233+ </ nav >
234+ </ li >
235+ < li class ="md-nav__item ">
236+ < a class ="md-nav__link " href ="#4-route-traffic ">
237+ < span class ="md-ellipsis ">
238+ 🔁 4. Route Traffic
239+ </ span >
240+ </ a >
241+ < nav aria-label ="🔁 4. Route Traffic " class ="md-nav ">
242+ < ul class ="md-nav__list ">
224243< li class ="md-nav__item ">
225- < a class ="md-nav__link " href ="#5-flip-traffic ">
244+ < a class ="md-nav__link " href ="#option-1-use-the-rest-api ">
226245< span class ="md-ellipsis ">
227- 🔁 5. Flip Traffic
246+ Option 1: Use the REST API
228247 </ span >
229248</ a >
230249</ li >
231250< li class ="md-nav__item ">
251+ < a class ="md-nav__link " href ="#option-2-mount-a-caddyfile ">
252+ < span class ="md-ellipsis ">
253+ Option 2: Mount a Caddyfile
254+ </ span >
255+ </ a >
256+ </ li >
257+ </ ul >
258+ </ nav >
259+ </ li >
260+ < li class ="md-nav__item ">
232261< a class ="md-nav__link " href ="#github-actions-example ">
233262< span class ="md-ellipsis ">
234263 ⚡ GitHub Actions Example
260289</ a >
261290</ li >
262291< li class ="md-nav__item ">
263- < a class ="md-nav__link " href ="#deployment-flow ">
292+ < a class ="md-nav__link " href ="#tasks ">
264293< span class ="md-ellipsis ">
265- 🔄 Deployment Flow
294+ 🔄 Tasks
266295 </ span >
267296</ a >
268297</ li >
281310</ a >
282311</ li >
283312< li class ="md-nav__item ">
284- < a class ="md-nav__link " href ="#3-deploying ">
313+ < a class ="md-nav__link " href ="#3-deploying-to-a-remote-server ">
314+ < span class ="md-ellipsis ">
315+ 🚀 3. Deploying to a Remote Server
316+ </ span >
317+ </ a >
318+ < nav aria-label ="🚀 3. Deploying to a Remote Server " class ="md-nav ">
319+ < ul class ="md-nav__list ">
320+ < li class ="md-nav__item ">
321+ < a class ="md-nav__link " href ="#1-deploy-the-proxy ">
285322< span class ="md-ellipsis ">
286- 🚀 3. Deploying
323+ 1. Deploy the Proxy
287324 </ span >
288325</ a >
289326</ li >
290327< li class ="md-nav__item ">
291- < a class ="md-nav__link " href ="#4 -deploy-the -new-app-stack ">
328+ < a class ="md-nav__link " href ="#2 -deploy-a -new-app-version ">
292329< span class ="md-ellipsis ">
293- 🆕 4 . Deploy the New App Stack
330+ 2 . Deploy a new App version
294331 </ span >
295332</ a >
296333</ li >
334+ </ ul >
335+ </ nav >
336+ </ li >
297337< li class ="md-nav__item ">
298- < a class ="md-nav__link " href ="#5-flip -traffic ">
338+ < a class ="md-nav__link " href ="#4-route -traffic ">
299339< span class ="md-ellipsis ">
300- 🔁 5. Flip Traffic
340+ 🔁 4. Route Traffic
341+ </ span >
342+ </ a >
343+ < nav aria-label ="🔁 4. Route Traffic " class ="md-nav ">
344+ < ul class ="md-nav__list ">
345+ < li class ="md-nav__item ">
346+ < a class ="md-nav__link " href ="#option-1-use-the-rest-api ">
347+ < span class ="md-ellipsis ">
348+ Option 1: Use the REST API
301349 </ span >
302350</ a >
303351</ li >
304352< li class ="md-nav__item ">
353+ < a class ="md-nav__link " href ="#option-2-mount-a-caddyfile ">
354+ < span class ="md-ellipsis ">
355+ Option 2: Mount a Caddyfile
356+ </ span >
357+ </ a >
358+ </ li >
359+ </ ul >
360+ </ nav >
361+ </ li >
362+ < li class ="md-nav__item ">
305363< a class ="md-nav__link " href ="#github-actions-example ">
306364< span class ="md-ellipsis ">
307365 ⚡ GitHub Actions Example
@@ -323,47 +381,49 @@ <h1 id="advanced-deployments">⚙️ Advanced Deployments</h1>
323381< li > No way to test a new version while another is live (blue/green)</ li >
324382< li > No quick rollback once upgraded</ li >
325383</ ul >
326- < p > When your app is ready for production, you can enable a traffic router to
327- eliminate these issues.</ p >
384+ < p > When your app is ready for production, you can enable a simple < strong > traffic
385+ router </ strong > to eliminate these issues.</ p >
328386< h2 id ="how-it-works "> 🧭 How It Works</ h2 >
329- < p > The traffic router is a lightweight proxy project ( already included with
330- SuperStack) that sits in front of your app. Its responsibilities:</ p >
387+ < p > The traffic router is a lightweight proxy already included with SuperStack that
388+ sits in front of your app. Its responsibilities:</ p >
331389< ul >
332- < li > Route traffic to the active app stack </ li >
390+ < li > Route traffic to the correct app</ li >
333391< li > Simplify switching between versions</ li >
334392< li > Handle TLS termination</ li >
335393</ ul >
336394< pre class ="mermaid "> < code > flowchart TD
395+ FormerApp["Former App"]
337396 Proxy["Traffic Router"]
338397 Proxy --> LiveApp["Live App"]
339398 NextApp["Next App"]
340399</ code > </ pre >
341400< p > Normally the app exposes ports directly, but in this advanced mode, the < strong > proxy
342401owns the ports</ strong > , and apps connect to its Docker network.</ p >
343- < h2 id ="deployment-flow "> 🔄 Deployment Flow </ h2 >
402+ < h2 id ="tasks "> 🔄 Tasks </ h2 >
344403< ol >
345- < li > Enable the proxy project (included in the repository).</ li >
346- < li > Stop exposing ports in the app — only the proxy will listen on < code > :80</ code > and
347- < code > :443</ code > .</ li >
348- < li > For each deployment, bring up a new app stack (e.g. < code > app/<commit></ code > ),
349- connected to the proxy’s network.</ li >
350- < li > Test the new app while the current one remains live.</ li >
351- < li > Flip traffic in the proxy to point to the new app.</ li >
352- < li > Tear down the old one when ready.</ li >
404+ < li > Enable the proxy project (included in SuperStack)</ li >
405+ < li > Stop exposing ports in the app</ li >
406+ < li > Instead, connect to the proxy's Docker network</ li >
407+ < li > The proxy will listen on < code > :80</ code > and < code > :443</ code > </ li >
353408</ ol >
409+ < p > The application still manages api routes, auth, etc.</ p >
354410< h2 id ="1-start-the-proxy "> 🧱 1. Start the Proxy</ h2 >
355- < p > A < code > proxy</ code > project already exists in your SuperStack project.</ p >
356411< blockquote >
357412< p > For consistent environments, use the proxy in all environments, including
358413development.</ p >
359414</ blockquote >
415+ < p > A < code > proxy</ code > project already exists in your SuperStack project.</ p >
416+ < p > First, stop your app to release the ports:</ p >
417+ < div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > app
418+ docker< span class ="w "> </ span > compose< span class ="w "> </ span > down
419+ </ code > </ pre > </ div >
360420< p > Start the proxy:</ p >
361- < div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > proxy
421+ < div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > ../ proxy
362422docker< span class ="w "> </ span > compose< span class ="w "> </ span > up< span class ="w "> </ span > -d
363423</ code > </ pre > </ div >
364424< h2 id ="2-adjust-the-application "> ⚙️ 2. Adjust the Application</ h2 >
365- < p > < strong > Remove the < code > ports:</ code > section from the app,</ strong > and connect it to the proxy's
366- network by adding these lines:</ p >
425+ < p > < strong > Remove the < code > ports:</ code > from the app,</ strong > and connect it to the proxy's network by
426+ adding these lines:</ p >
367427< div class ="highlight "> < span class ="filename "> app/compose.yaml</ span > < pre > < span > </ span > < code > < span class ="nt "> services</ span > < span class ="p "> :</ span >
368428< span class ="w "> </ span > < span class ="nt "> caddy</ span > < span class ="p "> :</ span >
369429< span class ="w "> </ span > < span class ="nt "> build</ span > < span class ="p "> :</ span >
@@ -375,29 +435,38 @@ <h2 id="2-adjust-the-application">⚙️ 2. Adjust the Application</h2>
375435</ span > < span class ="hll "> < span class ="w "> </ span > < span class ="nt "> proxy_default</ span > < span class ="p "> :</ span >
376436</ span > < span class ="hll "> < span class ="w "> </ span > < span class ="nt "> aliases</ span > < span class ="p "> :</ span >
377437</ span > < span class ="hll "> < span class ="w "> </ span > < span class ="p p-Indicator "> -</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> ${COMPOSE_PROJECT_NAME}_caddy</ span >
378- </ span >
379- < span class ="hll "> < span class ="nt "> networks</ span > < span class ="p "> :</ span >
438+ </ span > < span class =" hll " >
439+ </ span > < span class ="hll "> < span class ="nt "> networks</ span > < span class ="p "> :</ span >
380440</ span > < span class ="hll "> < span class ="w "> </ span > < span class ="nt "> proxy_default</ span > < span class ="p "> :</ span >
381441</ span > < span class ="hll "> < 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 >
382442</ span > </ code > </ pre > </ div >
383- < p > You should also remove the < code > ports:</ code > and < code > CADDY_SITE_ADDRESS</ code > in
443+ < p > Also remove the < code > ports:</ code > and < code > CADDY_SITE_ADDRESS</ code > in
384444< code > app/compose.override.yaml</ code > .</ p >
385445< p > What's Changed?</ p >
386446< ol >
387- < li > Exposed ports were removed. </ li >
388- < li > < code > CADDY_SITE_ADDRESS</ code > now listens internally on port < code > :80</ code > . </ li >
389- < li > The app joins the proxy's network so traffic can be routed to it. </ li >
447+ < li > Exposed ports were removed</ li >
448+ < li > < code > CADDY_SITE_ADDRESS</ code > now listens internally on port < code > :80</ code > </ li >
449+ < li > The app joins the proxy's network so traffic can be routed to it</ li >
390450< li > A container alias (< code > _caddy</ code > ) allows the proxy to target this service
391- reliably. </ li >
451+ reliably</ li >
392452</ ol >
393- < p > Recreate the app's Caddy container:</ p >
394- < div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > app
395- 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 > caddy
453+ < p > Bring up the app:</ p >
454+ < div class ="highlight "> < pre > < span > </ span > < code > docker< span class ="w "> </ span > compose< span class ="w "> </ span > up< span class ="w "> </ span > -d
455+ </ code > </ pre > </ div >
456+ < p > Test with:</ p >
457+ < div class ="highlight "> < pre > < span > </ span > < code > $< span class ="w "> </ span > curl< span class ="w "> </ span > http://localhost:8000/healthcheck
458+ OK
396459</ code > </ pre > </ div >
397460< p > Commit these changes – your app is now proxy-ready.</ p >
398- < h2 id ="3-deploying "> 🚀 3. Deploying</ h2 >
399- < p > The proxy is deployed once (usually manually), and each app is deployed
400- separately into its own directory.</ p >
461+ < h2 id ="3-deploying-to-a-remote-server "> 🚀 3. Deploying to a Remote Server</ h2 >
462+ < p > The proxy is deployed once (usually manually). After that, each app version is
463+ deployed separately into its own directory.</ p >
464+ < ol >
465+ < li > For each deployment, bring up a new app (e.g. < code > app/<commit></ code > )</ li >
466+ < li > Test the new app while the current one remains live</ li >
467+ < li > Flip traffic in the proxy to point to the new app</ li >
468+ < li > Tear down the old one when ready</ li >
469+ </ ol >
401470< div class ="highlight "> < pre > < span > </ span > < code > proxy/
402471 compose.yaml
403472app/
@@ -408,6 +477,7 @@ <h2 id="3-deploying">🚀 3. Deploying</h2>
408477 compose.yaml
409478 .env
410479</ code > </ pre > </ div >
480+ < h3 id ="1-deploy-the-proxy "> 1. Deploy the Proxy</ h3 >
411481< p > Before deploying, build and push your own proxy image by adding an < code > image:</ code >
412482line to the Compose file:</ p >
413483< div class ="highlight "> < span class ="filename "> proxy/compose.yaml</ span > < pre > < span > </ span > < code > < span class ="nt "> services</ span > < span class ="p "> :</ span >
@@ -429,28 +499,59 @@ <h2 id="3-deploying">🚀 3. Deploying</h2>
429499< p > Start the proxy:</ p >
430500< div class ="highlight "> < pre > < span > </ span > < code > docker< span class ="w "> </ span > compose< span class ="w "> </ span > up< span class ="w "> </ span > -d
431501</ code > </ pre > </ div >
432- < h2 id ="4-deploy-the-new-app-stack "> 🆕 4. Deploy the New App Stack</ h2 >
433- < p > Deploy your app into a new directory (e.g. < code > b/</ code > ):</ p >
502+ < h3 id ="2-deploy-a-new-app-version "> 2. Deploy a new App version</ h3 >
503+ < p > Deploy your app into a new directory (e.g. < code > app/b/</ code > ):</ p >
504+ < blockquote >
505+ < p > Important: Give it a unique directory name every time. Here we use 'b' for
506+ simplicity, but I recommend using the commit hash.</ p >
507+ </ blockquote >
434508< div class ="highlight "> < pre > < span > </ span > < code > mkdir< span class ="w "> </ span > app/b
435509</ code > </ pre > </ div >
510+ < p > Copy < code > compose.yaml</ code > (and secrets) there:</ p >
436511< div class ="highlight "> < pre > < span > </ span > < code > scp< span class ="w "> </ span > compose.yaml< span class ="w "> </ span > yourserver:app/b/
437512</ code > </ pre > </ div >
438- < p > Start it on the server:</ p >
513+ < p > Start the app on the server:</ p >
439514< div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > app/b
440515docker< span class ="w "> </ span > compose< span class ="w "> </ span > up< span class ="w "> </ span > -d
441516</ code > </ pre > </ div >
442- < p > Optionally, verify the new app is healthy before switching traffic:</ p >
517+ < p > Verify the new app is healthy before switching traffic:</ p >
443518< div class ="highlight "> < pre > < span > </ span > < code > $< span class ="w "> </ span > docker< span class ="w "> </ span > compose< span class ="w "> </ span > < span class ="nb "> exec</ span > < span class ="w "> </ span > -T< span class ="w "> </ span > caddy< span class ="w "> </ span > curl< span class ="w "> </ span > -fsS< span class ="w "> </ span > http://caddy:80/healthz
444519OK
445520</ code > </ pre > </ div >
446- < h2 id ="5-flip-traffic "> 🔁 5. Flip Traffic</ h2 >
521+ < h2 id ="4-route-traffic "> 🔁 4. Route Traffic</ h2 >
522+ < h3 id ="option-1-use-the-rest-api "> Option 1: Use the REST API</ h3 >
523+ < p > Caddy's configuration can be adjusted via HTTP using its < a href ="https://caddyserver.com/docs/api "> REST
524+ API</ a > .</ p >
525+ < p > Since the app was brought up in a directory named < code > b</ code > , the app's Caddy service
526+ was given the alias < code > b_caddy</ code > .</ p >
527+ < p > Direct all traffic to < code > b_caddy</ code > :</ p >
447528< div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > proxy
448- docker< span class ="w "> </ span > compose< span class ="w "> </ span > < span class ="nb "> exec</ span > < span class ="w "> </ span > caddy< span class ="w "> </ span > curl< span class ="w "> </ span > -X< span class ="w "> </ span > PATCH< span class ="w "> </ span > -d< span class ="w "> </ span > < span class ="s1 "> '"newapp_caddy :80"'</ span > < span class ="w "> </ span > < span class ="se "> \</ span >
529+ docker< span class ="w "> </ span > compose< span class ="w "> </ span > < span class ="nb "> exec</ span > < span class ="w "> </ span > caddy< span class ="w "> </ span > curl< span class ="w "> </ span > -X< span class ="w "> </ span > PATCH< span class ="w "> </ span > -d< span class ="w "> </ span > < span class ="s1 "> '"b_caddy :80"'</ span > < span class ="w "> </ span > < span class ="se "> \</ span >
449530< span class ="w "> </ span > http://localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/upstreams/0/dial
450531</ code > </ pre > </ div >
451532< p > Traffic now points to the new stack.</ p >
533+ < h3 id ="option-2-mount-a-caddyfile "> Option 2: Mount a Caddyfile</ h3 >
534+ < p > Create a simple Caddyfile on the server, such as:</ p >
535+ < div class ="highlight "> < span class ="filename "> proxy/caddy/Caddyfile</ span > < pre > < span > </ span > < code > {$CADDY_SITE_ADDRESS}
536+
537+ reverse_proxy b_caddy:80
538+ </ code > </ pre > </ div >
539+ < p > Mount it into the proxy's Caddy container:</ p >
540+ < div class ="highlight "> < span class ="filename "> proxy/compose.yaml</ span > < pre > < span > </ span > < code > < span class ="nt "> services</ span > < span class ="p "> :</ span >
541+ < span class ="w "> </ span > < span class ="nt "> caddy</ span > < span class ="p "> :</ span >
542+ < span class ="w "> </ span > < span class ="nt "> volumes</ span > < span class ="p "> :</ span >
543+ < span class ="hll "> < span class ="w "> </ span > < span class ="p p-Indicator "> -</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> ./caddy:/etc/caddy</ span >
544+ </ span > </ code > </ pre > </ div >
545+ < p > Recreate the container:</ p >
546+ < div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > proxy
547+ 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 > caddy
548+ </ code > </ pre > </ div >
549+ < p > After that, to re-route traffic, simply change the app service name in
550+ < code > proxy/caddy/Caddyfile</ code > and reload the proxy's configuration:</ p >
551+ < div class ="highlight "> < pre > < span > </ span > < code > docker< span class ="w "> </ span > compose< span class ="w "> </ span > < span class ="nb "> exec</ span > < span class ="w "> </ span > caddy< span class ="w "> </ span > caddy< span class ="w "> </ span > reload< span class ="w "> </ span > --config< span class ="w "> </ span > /etc/caddy/Caddyfile
552+ </ code > </ pre > </ div >
452553< h2 id ="github-actions-example "> ⚡ GitHub Actions Example</ h2 >
453- < p > Use this Github Actions Workflow to automate deployments. </ p >
554+ < p > Add this Github Actions workflow to automate deployments: </ p >
454555< details >
455556< summary > Show full workflow</ summary >
456557< div class ="highlight "> < span class ="filename "> .github/workflows/ci.yaml</ span > < pre > < span > </ span > < code > < span class ="nt "> name</ span > < span class ="p "> :</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> Deploy</ span >
0 commit comments