194194</ a >
195195</ li >
196196< li class ="md-nav__item ">
197- < a class ="md-nav__link " href ="#1-create-a-new- proxy-project ">
197+ < a class ="md-nav__link " href ="#1-start-the- proxy ">
198198< span class ="md-ellipsis ">
199- 1. Create a new proxy project
199+ 🧱 1. Start the Proxy
200200 </ span >
201201</ a >
202202</ li >
203203< li class ="md-nav__item ">
204204< a class ="md-nav__link " href ="#2-adjust-the-application ">
205205< span class ="md-ellipsis ">
206- 2. Adjust the Application
206+ ⚙️ 2. Adjust the Application
207207 </ span >
208208</ a >
209209</ li >
210210< li class ="md-nav__item ">
211- < a class ="md-nav__link " href ="#deploying ">
211+ < a class ="md-nav__link " href ="#3- deploying ">
212212< span class ="md-ellipsis ">
213- Deploying
213+ 🚀 3. Deploying
214214 </ span >
215215</ a >
216216</ li >
217217< li class ="md-nav__item ">
218- < a class ="md-nav__link " href ="#deploy-the-app ">
218+ < a class ="md-nav__link " href ="#4- deploy-the-new- app-stack ">
219219< span class ="md-ellipsis ">
220- Deploy the app
220+ 🆕 4. Deploy the New App Stack
221221 </ span >
222222</ a >
223- < nav aria-label ="Deploy the app " class ="md-nav ">
224- < ul class ="md-nav__list ">
223+ </ li >
225224< li class ="md-nav__item ">
226- < a class ="md-nav__link " href ="#flip-traffic ">
225+ < a class ="md-nav__link " href ="#5- flip-traffic ">
227226< span class ="md-ellipsis ">
228- Flip traffic
227+ 🔁 5. Flip Traffic
229228 </ span >
230229</ a >
231230</ li >
232- </ ul >
233- </ nav >
234- </ li >
235231< li class ="md-nav__item ">
236- < a class ="md-nav__link " href ="#github-actions-workflow ">
232+ < a class ="md-nav__link " href ="#github-actions-example ">
237233< span class ="md-ellipsis ">
238- Github Actions Workflow
234+ ⚡ GitHub Actions Example
239235 </ span >
240236</ a >
241237</ li >
271267</ a >
272268</ li >
273269< li class ="md-nav__item ">
274- < a class ="md-nav__link " href ="#1-create-a-new- proxy-project ">
270+ < a class ="md-nav__link " href ="#1-start-the- proxy ">
275271< span class ="md-ellipsis ">
276- 1. Create a new proxy project
272+ 🧱 1. Start the Proxy
277273 </ span >
278274</ a >
279275</ li >
280276< li class ="md-nav__item ">
281277< a class ="md-nav__link " href ="#2-adjust-the-application ">
282278< span class ="md-ellipsis ">
283- 2. Adjust the Application
279+ ⚙️ 2. Adjust the Application
284280 </ span >
285281</ a >
286282</ li >
287283< li class ="md-nav__item ">
288- < a class ="md-nav__link " href ="#deploying ">
284+ < a class ="md-nav__link " href ="#3- deploying ">
289285< span class ="md-ellipsis ">
290- Deploying
286+ 🚀 3. Deploying
291287 </ span >
292288</ a >
293289</ li >
294290< li class ="md-nav__item ">
295- < a class ="md-nav__link " href ="#deploy-the-app ">
291+ < a class ="md-nav__link " href ="#4- deploy-the-new- app-stack ">
296292< span class ="md-ellipsis ">
297- Deploy the app
293+ 🆕 4. Deploy the New App Stack
298294 </ span >
299295</ a >
300- < nav aria-label ="Deploy the app " class ="md-nav ">
301- < ul class ="md-nav__list ">
296+ </ li >
302297< li class ="md-nav__item ">
303- < a class ="md-nav__link " href ="#flip-traffic ">
298+ < a class ="md-nav__link " href ="#5- flip-traffic ">
304299< span class ="md-ellipsis ">
305- Flip traffic
300+ 🔁 5. Flip Traffic
306301 </ span >
307302</ a >
308303</ li >
309- </ ul >
310- </ nav >
311- </ li >
312304< li class ="md-nav__item ">
313- < a class ="md-nav__link " href ="#github-actions-workflow ">
305+ < a class ="md-nav__link " href ="#github-actions-example ">
314306< span class ="md-ellipsis ">
315- Github Actions Workflow
307+ ⚡ GitHub Actions Example
316308 </ span >
317309</ a >
318310</ li >
@@ -331,81 +323,45 @@ <h1 id="advanced-deployments">⚙️ Advanced Deployments</h1>
331323< li > No way to test a new version while another is live (blue/green)</ li >
332324< li > No quick rollback once upgraded</ li >
333325</ ul >
334- < p > When your app is ready for production, consider adding a < strong > traffic-switcher </ strong >
335- in front it .</ p >
326+ < p > When your app is ready for production, you can enable a < strong > traffic-router </ strong > in
327+ to eliminate downtime, enable blue/green testing and easy rollbacks .</ p >
336328< h2 id ="how-it-works "> 🧭 How It Works</ h2 >
337- < p > Instead of directly exposing ports from the app, you add a small proxy project
338- that sits in front of it.</ p >
339- < p > The proxy’s job is to:</ p >
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 >
340331< ul >
341- < li > Route incoming traffic to the active app stack</ li >
342- < li > Make it easy to flip traffic between app versions</ li >
343- < li > Terminate TLS</ li >
332+ < li > Route traffic to the active app stack</ li >
333+ < li > Simplify switching between versions</ li >
334+ < li > Handle TLS termination </ li >
344335</ ul >
345336< pre class ="mermaid "> < code > flowchart TD
346337 Proxy["Traffic Router"]
347338 Proxy --> LiveApp["Live App"]
348339 NextApp["Next App"]
349340</ code > </ pre >
341+ < p > In standard mode, the app exposes ports directly. In advanced mode, the < strong > proxy
342+ owns the ports</ strong > , and apps connect to it internally over Docker networks.</ p >
350343< h2 id ="deployment-flow "> 🔄 Deployment Flow</ h2 >
351344< ol >
352- < li > Stop exposing ports in the app project — only the proxy will listen on :80 and :443.</ li >
353- < li > Add a proxy project that runs Caddy (or another gateway).</ li >
354- < li > Each time you deploy, bring up a new app stack, connected to the proxy’s
355- network.</ li >
356- < li > Test the new stack while the old one is still live.</ li >
357- < li > Flip traffic in the proxy to point to the new stack.</ li >
345+ < li > Stop exposing ports in the app project — only the proxy will listen on < code > :80</ code >
346+ and < code > :443</ code > .</ li >
347+ < li > Enable the proxy project (included in the repository).</ 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 >
358352< li > Tear down the old one when ready.</ li >
359353</ ol >
360- < p > Ok, we need to make some changes to the repository.</ p >
361- < h2 id ="1-create-a-new-proxy-project "> 1. Create a new < code > proxy</ code > project</ h2 >
362- < p > From the root of the repository, create a new < code > proxy</ code > project:</ p >
363- < div class ="highlight "> < pre > < span > </ span > < code > mkdir< span class ="w "> </ span > proxy
364- </ code > </ pre > </ div >
365- < p > Give it a Compose file:</ p >
366- < div class ="highlight "> < span class ="filename "> proxy/compose.yaml</ span > < pre > < span > </ span > < code > < span class ="nt "> services</ span > < span class ="p "> :</ span >
367- < span class ="w "> </ span > < span class ="nt "> caddy</ span > < span class ="p "> :</ span >
368- < span class ="w "> </ span > < span class ="nt "> build</ span > < span class ="p "> :</ span >
369- < span class ="w "> </ span > < span class ="nt "> context</ span > < span class ="p "> :</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> ./caddy</ span >
370- < span class ="w "> </ span > < span class ="nt "> ports</ span > < span class ="p "> :</ span >
371- < span class ="w "> </ span > < span class ="p p-Indicator "> -</ span > < span class ="w "> </ span > < span class ="s "> "80:80"</ span >
372- < span class ="w "> </ span > < span class ="p p-Indicator "> -</ span > < span class ="w "> </ span > < span class ="s "> "443:443"</ span >
373- < span class ="w "> </ span > < span class ="nt "> volumes</ span > < span class ="p "> :</ span >
374- < span class ="w "> </ span > < span class ="p p-Indicator "> -</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> caddy_data:/data</ span >
375- < span class ="w "> </ span > < span class ="nt "> environment</ span > < span class ="p "> :</ span >
376- < span class ="w "> </ span > < span class ="nt "> CADDY_SITE_ADDRESS</ span > < span class ="p "> :</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> api.myapp.com</ span >
377-
378- < span class ="nt "> volumes</ span > < span class ="p "> :</ span >
379- < span class ="w "> </ span > < span class ="nt "> caddy_data</ span > < span class ="p "> :</ span >
380- < span class ="w "> </ span > < span class ="nt "> name</ span > < span class ="p "> :</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> caddy-data</ span > < span class ="w "> </ span > < span class ="c1 "> # Persistent volume for certificates</ span >
381- </ code > </ pre > </ div >
382- < p > Add development overrides:</ p >
383- < div class ="highlight "> < span class ="filename "> proxy/compose.override.yaml</ span > < pre > < span > </ span > < code > < span class ="c1 "> # Development overrides</ span >
384-
385- < span class ="nt "> services</ span > < span class ="p "> :</ span >
386- < span class ="w "> </ span > < span class ="nt "> caddy</ span > < span class ="p "> :</ span >
387- < span class ="w "> </ span > < span class ="nt "> ports</ span > < span class ="p "> :</ span >
388- < span class ="w "> </ span > < span class ="p p-Indicator "> -</ span > < span class ="w "> </ span > < span class ="s "> "8000:80"</ span >
389- < span class ="w "> </ span > < span class ="nt "> environment</ span > < span class ="p "> :</ span >
390- < span class ="w "> </ span > < span class ="nt "> CADDY_SITE_ADDRESS</ span > < span class ="p "> :</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> :80</ span >
391- </ code > </ pre > </ div >
392- < p > Configure Caddy:</ p >
393- < div class ="highlight "> < pre > < span > </ span > < code > mkdir< span class ="w "> </ span > proxy/caddy
394- </ code > </ pre > </ div >
395- < div class ="highlight "> < span class ="filename "> proxy/caddy/Caddyfile</ span > < pre > < span > </ span > < code > < span class ="p p-Indicator "> {</ span > < span class ="nv "> $CADDY_SITE_ADDRESS</ span > < span class ="p p-Indicator "> }</ span >
396-
397- < span class ="l l-Scalar l-Scalar-Plain "> reverse_proxy app_caddy:80</ span >
398- </ code > </ pre > </ div >
399- < p > Add a Dockerfile:</ p >
400- < div class ="highlight "> < span class ="filename "> proxy/caddy/Dockerfile</ span > < pre > < span > </ span > < code > < span class ="k "> FROM</ span > < span class ="w "> </ span > < span class ="s "> caddy:2</ span >
401-
402- < span class ="k "> COPY</ span > < span class ="w "> </ span > Caddyfile< span class ="w "> </ span > /etc/caddy/Caddyfile
403- </ code > </ pre > </ div >
404- < p > Start the proxy service:</ p >
405- < div class ="highlight "> < pre > < span > </ span > < code > < span class ="l l-Scalar l-Scalar-Plain "> docker compose up -d</ span >
354+ < 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 >
356+ < blockquote >
357+ < p > For consistent environments, use the proxy in all environments including
358+ development.</ p >
359+ </ blockquote >
360+ < p > Start it:</ p >
361+ < div class ="highlight "> < pre > < span > </ span > < code > docker< span class ="w "> </ span > compose< span class ="w "> </ span > up< span class ="w "> </ span > -d
406362</ code > </ pre > </ div >
407- < h2 id ="2-adjust-the-application "> 2. Adjust the Application</ h2 >
408- < p > Remove the app's exposed ports, and connect to the proxy's network:</ p >
363+ < h2 id ="2-adjust-the-application "> ⚙️ 2. Adjust the Application</ h2 >
364+ < p > Remove the app's exposed ports and connect it to the proxy's network:</ p >
409365< div class ="highlight "> < span class ="filename "> app/compose.yaml</ span > < pre > < span > </ span > < code > < span class ="nt "> services</ span > < span class ="p "> :</ span >
410366< span class ="w "> </ span > < span class ="nt "> caddy</ span > < span class ="p "> :</ span >
411367< span class ="w "> </ span > < span class ="nt "> build</ span > < span class ="p "> :</ span >
@@ -422,22 +378,22 @@ <h2 id="2-adjust-the-application">2. Adjust the Application</h2>
422378</ span > < span class ="hll "> < span class ="w "> </ span > < span class ="nt "> proxy_default</ span > < span class ="p "> :</ span >
423379</ 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 >
424380</ span > </ code > </ pre > </ div >
425- < p > What's changed ?</ p >
381+ < p > What's Changed ?</ p >
426382< ol >
427- < li > The exposed ports were removed.</ li >
428- < li > Caddy's site address has changed to < code > :80</ code > (The application layer no longer
429- handles TLS).</ li >
430- < li > We connect to the proxy's network, so the proxy can direct traffic to the
431- app.</ li >
432- < li > A container alias was added. This alias allows the proxy to target this
433- container, while still allowing Docker to manage the container name.</ li >
383+ < li > Exposed ports were removed.</ li >
384+ < li > < code > CADDY_SITE_ADDRESS</ code > now listens internally on port < code > :80</ code > .</ li >
385+ < li > The app joins the proxy's network so traffic can be routed to it.</ li >
386+ < li > A container alias (< code > _caddy</ code > ) lets the proxy target this service reliably.</ li >
434387</ ol >
435- < p > The < code > CADDY_SITE_ADDRESS</ code > environment variable can be removed from the override
436- file.</ p >
437- < div class ="highlight "> < pre > < span > </ span > < code > docker< span class ="w "> </ span > compose< span class ="w "> </ span > up< span class ="w "> </ span > -d< span class ="w "> </ span > app
388+ < p > You can also remove the < code > CADDY_SITE_ADDRESS</ code > override in
389+ < code > compose.override.yaml</ code > .</ p >
390+ < p > Recreate the app's Caddy container:</ p >
391+ < div class ="highlight "> < pre > < span > </ span > < code > 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
438392</ code > </ pre > </ div >
439- < p > Commit these changes.</ p >
440- < h2 id ="deploying "> Deploying</ h2 >
393+ < p > Commit these changes – your app is now "proxy-ready".</ p >
394+ < h2 id ="3-deploying "> 🚀 3. Deploying</ h2 >
395+ < p > The proxy is deployed once (usually manually), and each app is deployed
396+ separately into its own directory.</ p >
441397< div class ="highlight "> < pre > < span > </ span > < code > proxy/
442398 compose.yaml
443399app/
@@ -448,25 +404,46 @@ <h2 id="deploying">Deploying</h2>
448404 compose.yaml
449405 .env
450406</ code > </ pre > </ div >
451- < p > The proxy is deployed once.</ p >
452- < p > On the server, create a proxy directory:</ p >
407+ < p > Before deploying, build and push your own proxy image by adding an image name
408+ to the Compose file:</ p >
409+ < div class ="highlight "> < span class ="filename "> proxy/compose.yaml</ span > < pre > < span > </ span > < code > < span class ="nt "> services</ span > < span class ="p "> :</ span >
410+ < span class ="w "> </ span > < span class ="nt "> caddy</ span > < span class ="p "> :</ span >
411+ < span class ="w "> </ span > < span class ="nt "> build</ span > < span class ="p "> :</ span >
412+ < span class ="w "> </ span > < span class ="nt "> context</ span > < span class ="p "> :</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> ./caddy</ span >
413+ < span class ="hll "> < span class ="w "> </ span > < span class ="nt "> image</ span > < span class ="p "> :</ span > < span class ="w "> </ span > < span class ="l l-Scalar l-Scalar-Plain "> ghcr.io/youruser/yourapp-proxy:0.1.0</ span >
414+ </ span > </ code > </ pre > </ div >
415+ < p > Build and push it:</ p >
416+ < div class ="highlight "> < pre > < span > </ span > < code > docker< span class ="w "> </ span > compose< span class ="w "> </ span > build
417+ docker< span class ="w "> </ span > compose< span class ="w "> </ span > push
418+ </ code > </ pre > </ div >
419+ < p > Create a proxy directory on the server:</ p >
453420< div class ="highlight "> < pre > < span > </ span > < code > mkdir< span class ="w "> </ span > proxy
454421</ code > </ pre > </ div >
455- < p > Copy your Compose file to the server :</ p >
422+ < p > Copy the proxy's Compose file:</ p >
456423< div class ="highlight "> < pre > < span > </ span > < code > scp< span class ="w "> </ span > proxy/compose.yaml< span class ="w "> </ span > app-backend:proxy/
457424</ code > </ pre > </ div >
458- < blockquote >
459- < p > You might also point a second hostname to an idle stack for testing.</ p >
460- </ blockquote >
461- < h2 id ="deploy-the-app "> Deploy the app</ h2 >
462- < div class ="highlight "> < pre > < span > </ span > < code > docker< span class ="w "> </ span > compose< span class ="w "> </ span > up< span class ="w "> </ span > -d
425+ < p > Start the proxy:</ p >
426+ < p > docker compose up -d</ p >
427+ < h2 id ="4-deploy-the-new-app-stack "> 🆕 4. Deploy the New App Stack</ h2 >
428+ < p > Deploy your app into a new directory (e.g. < code > b/</ code > ):</ p >
429+ < div class ="highlight "> < pre > < span > </ span > < code > scp< span class ="w "> </ span > compose.yaml< span class ="w "> </ span > yourserver:app/b/
430+ </ code > </ pre > </ div >
431+ < p > Start it on the server:</ p >
432+ < div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > app/b
433+ docker< span class ="w "> </ span > compose< span class ="w "> </ span > up< span class ="w "> </ span > -d
434+ </ code > </ pre > </ div >
435+ < p > Optionally, verify it's healthy before switching traffic:</ p >
436+ < 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 > -T< span class ="w "> </ span > caddy< span class ="w "> </ span > curl< span class ="w "> </ span > -fsS< span class ="w "> </ span > http://caddy:80/healthz
463437</ code > </ pre > </ div >
464- < h3 id ="flip-traffic "> Flip traffic </ h3 >
438+ < h2 id ="5- flip-traffic "> 🔁 5. Flip Traffic </ h2 >
465439< div class ="highlight "> < pre > < span > </ span > < code > < span class ="nb "> cd</ span > < span class ="w "> </ span > proxy
466440docker< 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 >
467441< span class ="w "> </ span > http://localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/upstreams/0/dial
468442</ code > </ pre > </ div >
469- < h2 id ="github-actions-workflow "> Github Actions Workflow</ h2 >
443+ < p > Traffic now points to the new stack.</ p >
444+ < h2 id ="github-actions-example "> ⚡ GitHub Actions Example</ h2 >
445+ < details >
446+ < summary > Show full workflow</ summary >
470447< 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 >
471448
472449< span class ="nt "> on</ span > < span class ="p "> :</ span >
@@ -542,6 +519,7 @@ <h2 id="github-actions-workflow">Github Actions Workflow</h2>
542519< span class ="w "> </ span > < span class ="no "> mkdir -p /var/log/sku-generator</ span >
543520< span class ="w "> </ span > < span class ="no "> echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ") ${{ github.sha }}" >> /var/log/sku-generator/deploy.log</ span >
544521</ code > </ pre > </ div >
522+ </ details >
545523</ article >
546524</ div >
547525< script > var target = document . getElementById ( location . hash . slice ( 1 ) ) ; target && target . name && ( target . checked = target . name . startsWith ( "__tabbed_" ) ) </ script >
0 commit comments