Modern CI/CD for Umbraco with Azure DevOps and On-Premises IIS
November 25, 2025

A few years ago, I shared a walkthrough showing how to deploy Umbraco websites to on-premises servers using classic Azure DevOps Release Pipelines. A lot has changed since then. Today, Umbraco runs on modern .NET, most projects use Vite or Tailwind for their frontend tooling, and Azure DevOps strongly encourages YAML-based pipelines instead of the classic UI-driven releases.
This updated guide reflects how we build and ship Umbraco websites in 2025 — using full automation, environment-aware deployments, and a single YAML pipeline that deploys to UAT and Production IIS servers inside your own datacenter.
Whether you're modernising an old deployment process or starting from scratch, this article gives you a solid blueprint. Just keep in mind: this is a boilerplate. Every organisation has its own requirements — load balancing, replication, shared media strategies, secrets handling, security hardening, and deployment governance. You will almost always need to adapt this workflow to match your exact infrastructure.
Many teams continue to host Umbraco on their own infrastructure — sometimes for compliance reasons, sometimes for integration with local systems, and sometimes because cloud migration simply isn’t practical. Developers, however, expect cloud-like automation regardless of where the servers run.
CI/CD pipelines provide:
Predictable deployments
Safe rollouts
Zero manual file copying
Consistent server configuration
Developer-friendly automation
Azure DevOps (Cloud)
Git repository of your Umbraco solution
Multi-stage YAML pipeline
Azure DevOps Environments (UAT & PROD)
On-Premises Servers
Windows Server with IIS
Self-hosted Azure DevOps agents
Dedicated UAT & PROD wwwroot folders
Deployment Flow
Developer pushes to dev or release/uat
Pipeline builds the app (.NET + npm)
Output deployed to UAT
Same artifact promoted to Production
1. Create Agent Pool
𝙾𝚛𝚐𝚊𝚗𝚒𝚣𝚊𝚝𝚒𝚘𝚗 𝚂𝚎𝚝𝚝𝚒𝚗𝚐𝚜 → 𝙰𝚐𝚎𝚗𝚝 𝙿𝚘𝚘𝚕𝚜 → 𝙽𝚎𝚠 → 𝚂𝚎𝚕𝚏-𝚑𝚘𝚜𝚝𝚎𝚍
2. Download the Agent
𝙽𝚎𝚠 𝙰𝚐𝚎𝚗𝚝 → 𝚆𝚒𝚗𝚍𝚘𝚠𝚜 → 𝙳𝚘𝚠𝚗𝚕𝚘𝚊𝚍 𝚉𝙸𝙿
3. Configure the Agent
𝚖𝚔𝚍𝚒𝚛 𝙲:\𝚊𝚣𝚍𝚘-𝚊𝚐𝚎𝚗𝚝
𝚌𝚍 𝙲:\𝚊𝚣𝚍𝚘-𝚊𝚐𝚎𝚗𝚝
Extract ZIP and run:
.\𝚌𝚘𝚗𝚏𝚒𝚐.𝚌𝚖𝚍
Then:
Server URL: https://dev.azure.com/YOURORG/
PAT: must have Agent Pools (read, manage)
Pool: OnPrem-IIS
Name: UAT-IIS or PROD-IIS
Run as service: Yes
Start the agent:
.\𝚛𝚞𝚗.𝚌𝚖𝚍
Pipelines → Environments
Create UAT-IIS and register UAT server
Create PROD-IIS and register PROD server
Fully functional pipeline below — copy/paste as-is.
𝚝𝚛𝚒𝚐𝚐𝚎𝚛:
- 𝚍𝚎𝚟
- 𝚛𝚎𝚕𝚎𝚊𝚜𝚎/𝚞𝚊𝚝
𝚟𝚊𝚛𝚒𝚊𝚋𝚕𝚎𝚜:
𝚋𝚞𝚒𝚕𝚍𝙲𝚘𝚗𝚏𝚒𝚐𝚞𝚛𝚊𝚝𝚒𝚘𝚗: '𝚁𝚎𝚕𝚎𝚊𝚜𝚎'
𝚙𝚛𝚘𝚓𝚎𝚌𝚝𝙵𝚘𝚕𝚍𝚎𝚛: '𝚠𝚠𝚠'
𝚙𝚞𝚋𝚕𝚒𝚜𝚑𝙵𝚘𝚕𝚍𝚎𝚛: '$(𝙱𝚞𝚒𝚕𝚍.𝙰𝚛𝚝𝚒𝚏𝚊𝚌𝚝𝚂𝚝𝚊𝚐𝚒𝚗𝚐𝙳𝚒𝚛𝚎𝚌𝚝𝚘𝚛𝚢)/𝚙𝚞𝚋𝚕𝚒𝚜𝚑'
𝚗𝚘𝚍𝚎𝚅𝚎𝚛𝚜𝚒𝚘𝚗: '𝟸𝟶.𝚡'
𝚘𝚏𝚏𝚕𝚒𝚗𝚎𝚃𝚎𝚖𝚙𝚕𝚊𝚝𝚎: '𝙰𝚙𝚙_𝙾𝚏𝚏𝚕𝚒𝚗𝚎.𝚑𝚝𝚖.𝚛𝚎𝚗𝚊𝚖𝚎'
𝚘𝚏𝚏𝚕𝚒𝚗𝚎𝙵𝚒𝚕𝚎: '𝙰𝚙𝚙_𝙾𝚏𝚏𝚕𝚒𝚗𝚎.𝚑𝚝𝚖'
𝚜𝚝𝚊𝚐𝚎𝚜:
- 𝚜𝚝𝚊𝚐𝚎: 𝙱𝚞𝚒𝚕𝚍
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙱𝚞𝚒𝚕𝚍 & 𝙿𝚞𝚋𝚕𝚒𝚜𝚑 𝚄𝚖𝚋𝚛𝚊𝚌𝚘'
𝚓𝚘𝚋𝚜:
- 𝚓𝚘𝚋: 𝙱𝚞𝚒𝚕𝚍𝙹𝚘𝚋
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙱𝚞𝚒𝚕𝚍 .𝙽𝙴𝚃 + 𝚃𝚊𝚒𝚕𝚠𝚒𝚗𝚍 + 𝚅𝚒𝚝𝚎'
𝚙𝚘𝚘𝚕:
𝚟𝚖𝙸𝚖𝚊𝚐𝚎: '𝚠𝚒𝚗𝚍𝚘𝚠𝚜-𝚕𝚊𝚝𝚎𝚜𝚝'
𝚜𝚝𝚎𝚙𝚜:
- 𝚌𝚑𝚎𝚌𝚔𝚘𝚞𝚝: 𝚜𝚎𝚕𝚏
- 𝚝𝚊𝚜𝚔: 𝙽𝚘𝚍𝚎𝚃𝚘𝚘𝚕@𝟶
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙸𝚗𝚜𝚝𝚊𝚕𝚕 𝙽𝚘𝚍𝚎.𝚓𝚜'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚟𝚎𝚛𝚜𝚒𝚘𝚗𝚂𝚙𝚎𝚌: '$(𝚗𝚘𝚍𝚎𝚅𝚎𝚛𝚜𝚒𝚘𝚗)'
- 𝚝𝚊𝚜𝚔: 𝙲𝚊𝚌𝚑𝚎@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙲𝚊𝚌𝚑𝚎 𝚗𝚙𝚖 (𝚏𝚛𝚘𝚗𝚝𝚎𝚗𝚍)'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚔𝚎𝚢: '𝚗𝚙𝚖-𝚏𝚛𝚘𝚗𝚝𝚎𝚗𝚍 | "$(𝙱𝚞𝚒𝚕𝚍. 𝚂𝚘𝚞𝚛𝚌𝚎𝚜𝙳𝚒𝚛𝚎𝚌𝚝𝚘𝚛𝚢)/$(𝚙𝚛𝚘𝚓𝚎𝚌𝚝𝙵𝚘𝚕𝚍𝚎𝚛)/𝚙𝚊𝚌𝚔𝚊𝚐𝚎-𝚕𝚘𝚌𝚔.𝚓𝚜𝚘𝚗"'
𝚙𝚊𝚝𝚑: '$(𝙱𝚞𝚒𝚕𝚍.𝚂𝚘𝚞𝚛𝚌𝚎𝚜𝙳𝚒𝚛𝚎𝚌𝚝𝚘𝚛𝚢)/$(𝚙𝚛𝚘𝚓𝚎𝚌𝚝𝙵𝚘𝚕𝚍𝚎𝚛)/𝚗𝚘𝚍𝚎_𝚖𝚘𝚍𝚞𝚕𝚎𝚜'
- 𝚝𝚊𝚜𝚔: 𝙲𝚊𝚌𝚑𝚎@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙲𝚊𝚌𝚑𝚎 𝚗𝚙𝚖 (𝚋𝚊𝚌𝚔𝚘𝚏𝚏𝚒𝚌𝚎)'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚔𝚎𝚢: '𝚗𝚙𝚖-𝚋𝚊𝚌𝚔𝚎𝚗𝚍 | "$(𝙱𝚞𝚒𝚕𝚍.𝚂𝚘𝚞𝚛𝚌𝚎𝚜𝙳𝚒𝚛𝚎𝚌𝚝𝚘𝚛𝚢)/𝚍𝚋𝚕.𝙼𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊.𝚋𝚊𝚌𝚔𝚘𝚏𝚏𝚒𝚌𝚎/𝚙𝚊𝚌𝚔𝚊𝚐𝚎-𝚕𝚘𝚌𝚔.𝚓𝚜𝚘𝚗"'
𝚙𝚊𝚝𝚑: '$(𝙱𝚞𝚒𝚕𝚍.𝚂𝚘𝚞𝚛𝚌𝚎𝚜𝙳𝚒𝚛𝚎𝚌𝚝𝚘𝚛𝚢)/𝚍𝚋𝚕.𝙼𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊.𝚋𝚊𝚌𝚔𝚘𝚏𝚏𝚒𝚌𝚎/𝚗𝚘𝚍𝚎_𝚖𝚘𝚍𝚞𝚕𝚎𝚜'
- 𝚝𝚊𝚜𝚔: 𝙽𝚙𝚖@𝟷
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝚗𝚙𝚖 𝚒𝚗𝚜𝚝𝚊𝚕𝚕 (𝚋𝚊𝚌𝚔𝚘𝚏𝚏𝚒𝚌𝚎)'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚌𝚘𝚖𝚖𝚊𝚗𝚍: '𝚒𝚗𝚜𝚝𝚊𝚕𝚕'
𝚠𝚘𝚛𝚔𝚒𝚗𝚐𝙳𝚒𝚛: '𝚍𝚋𝚕.𝙼𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊.𝚋𝚊𝚌𝚔𝚘𝚏𝚏𝚒𝚌𝚎'
- 𝚝𝚊𝚜𝚔: 𝚄𝚜𝚎𝙳𝚘𝚝𝙽𝚎𝚝@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙸𝚗𝚜𝚝𝚊𝚕𝚕 .𝙽𝙴𝚃 𝟿 𝚂𝙳𝙺'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚙𝚊𝚌𝚔𝚊𝚐𝚎𝚃𝚢𝚙𝚎: '𝚜𝚍𝚔'
𝚟𝚎𝚛𝚜𝚒𝚘𝚗: '𝟿.𝟶.𝚡'
- 𝚝𝚊𝚜𝚔: 𝙳𝚘𝚝𝙽𝚎𝚝𝙲𝚘𝚛𝚎𝙲𝙻𝙸@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝚁𝚎𝚜𝚝𝚘𝚛𝚎 𝙽𝚞𝙶𝚎𝚝'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚌𝚘𝚖𝚖𝚊𝚗𝚍: '𝚛𝚎𝚜𝚝𝚘𝚛𝚎'
𝚙𝚛𝚘𝚓𝚎𝚌𝚝𝚜: '$(𝚙𝚛𝚘𝚓𝚎𝚌𝚝𝙵𝚘𝚕𝚍𝚎𝚛)/𝚠𝚠𝚠.𝚌𝚜𝚙𝚛𝚘𝚓'
- 𝚝𝚊𝚜𝚔: 𝙳𝚘𝚝𝙽𝚎𝚝𝙲𝚘𝚛𝚎𝙲𝙻𝙸@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙿𝚞𝚋𝚕𝚒𝚜𝚑'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚌𝚘𝚖𝚖𝚊𝚗𝚍: '𝚙𝚞𝚋𝚕𝚒𝚜𝚑'
𝚙𝚛𝚘𝚓𝚎𝚌𝚝𝚜: '$(𝚙𝚛𝚘𝚓𝚎𝚌𝚝𝙵𝚘𝚕𝚍𝚎𝚛)/𝚠𝚠𝚠.𝚌𝚜𝚙𝚛𝚘𝚓'
𝚊𝚛𝚐𝚞𝚖𝚎𝚗𝚝𝚜: '--𝚌𝚘𝚗𝚏𝚒𝚐𝚞𝚛𝚊𝚝𝚒𝚘𝚗 $(𝚋𝚞𝚒𝚕𝚍𝙲𝚘𝚗𝚏𝚒𝚐𝚞𝚛𝚊𝚝𝚒𝚘𝚗) --𝚘𝚞𝚝𝚙𝚞𝚝 $(𝚙𝚞𝚋𝚕𝚒𝚜𝚑𝙵𝚘𝚕𝚍𝚎𝚛)'
𝚣𝚒𝚙𝙰𝚏𝚝𝚎𝚛𝙿𝚞𝚋𝚕𝚒𝚜𝚑: 𝚏𝚊𝚕𝚜𝚎
- 𝚙𝚞𝚋𝚕𝚒𝚜𝚑: $(𝚙𝚞𝚋𝚕𝚒𝚜𝚑𝙵𝚘𝚕𝚍𝚎𝚛)
𝚊𝚛𝚝𝚒𝚏𝚊𝚌𝚝: 𝚍𝚛𝚘𝚙
- 𝚜𝚝𝚊𝚐𝚎: 𝙳𝚎𝚙𝚕𝚘𝚢_𝚄𝙰𝚃
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙳𝚎𝚙𝚕𝚘𝚢 𝚝𝚘 𝚄𝙰𝚃'
𝚍𝚎𝚙𝚎𝚗𝚍𝚜𝙾𝚗: 𝙱𝚞𝚒𝚕𝚍
𝚌𝚘𝚗𝚍𝚒𝚝𝚒𝚘𝚗: 𝚜𝚞𝚌𝚌𝚎𝚎𝚍𝚎𝚍()
𝚓𝚘𝚋𝚜:
- 𝚍𝚎𝚙𝚕𝚘𝚢𝚖𝚎𝚗𝚝: 𝚄𝙰𝚃𝙳𝚎𝚙𝚕𝚘𝚢
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙳𝚎𝚙𝚕𝚘𝚢 𝚝𝚘 𝚄𝙰𝚃`
𝚎𝚗𝚟𝚒𝚛𝚘𝚗𝚖𝚎𝚗𝚝:
𝚗𝚊𝚖𝚎: '𝚄𝙰𝚃-𝙳𝙱𝙻𝟶𝟷'
𝚛𝚎𝚜𝚘𝚞𝚛𝚌𝚎𝚃𝚢𝚙𝚎: 𝚅𝚒𝚛𝚝𝚞𝚊𝚕𝙼𝚊𝚌𝚑𝚒𝚗𝚎
𝚜𝚝𝚛𝚊𝚝𝚎𝚐𝚢:
𝚛𝚞𝚗𝙾𝚗𝚌𝚎:
𝚍𝚎𝚙𝚕𝚘𝚢:
𝚜𝚝𝚎𝚙𝚜:
- 𝚌𝚑𝚎𝚌𝚔𝚘𝚞𝚝: 𝚗𝚘𝚗𝚎
- 𝚍𝚘𝚠𝚗𝚕𝚘𝚊𝚍: 𝚌𝚞𝚛𝚛𝚎𝚗𝚝
𝚊𝚛𝚝𝚒𝚏𝚊𝚌𝚝: 𝚍𝚛𝚘𝚙
- 𝚝𝚊𝚜𝚔: 𝙿𝚘𝚠𝚎𝚛𝚂𝚑𝚎𝚕𝚕@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙰𝚌𝚝𝚒𝚟𝚊𝚝𝚎 𝙰𝚙𝚙_𝙾𝚏𝚏𝚕𝚒𝚗𝚎'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚝𝚊𝚛𝚐𝚎𝚝𝚃𝚢𝚙𝚎: '𝚒𝚗𝚕𝚒𝚗𝚎'
𝚜𝚌𝚛𝚒𝚙𝚝: |
$𝚛𝚘𝚘𝚝="𝙳:\𝚆𝙴𝙱\𝙼𝚊𝚍𝚎𝚒𝚛𝚒𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊\𝚞𝚊𝚝.𝚖𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝚕𝚎𝚒𝚛𝚒𝚊.𝚙𝚝\𝚠𝚠𝚠𝚛𝚘𝚘𝚝"
$𝚝𝚎𝚖𝚙𝚕𝚊𝚝𝚎=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚛𝚘𝚘𝚝 "$(𝚘𝚏𝚏𝚕𝚒𝚗𝚎𝚃𝚎𝚖𝚙𝚕𝚊𝚝𝚎)"
$𝚘𝚏𝚏𝚕𝚒𝚗𝚎=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚛𝚘𝚘𝚝 "$(𝚘𝚏𝚏𝚕𝚒𝚗𝚎𝙵𝚒𝚕𝚎)"
𝚒𝚏(𝚃𝚎𝚜𝚝-𝙿𝚊𝚝𝚑 $𝚝𝚎𝚖𝚙𝚕𝚊𝚝𝚎){
𝚒𝚏(𝚃𝚎𝚜𝚝-𝙿𝚊𝚝𝚑 $𝚘𝚏𝚏𝚕𝚒𝚗𝚎){ 𝚁𝚎𝚖𝚘𝚟𝚎-𝙸𝚝𝚎𝚖 $𝚘𝚏𝚏𝚕𝚒𝚗𝚎 -𝙵𝚘𝚛𝚌𝚎 }
𝚁𝚎𝚗𝚊𝚖𝚎-𝙸𝚝𝚎𝚖 $𝚝𝚎𝚖𝚙𝚕𝚊𝚝𝚎 $𝚘𝚏𝚏𝚕𝚒𝚗𝚎 -𝙵𝚘𝚛𝚌𝚎
}
- 𝚝𝚊𝚜𝚔: 𝙿𝚘𝚠𝚎𝚛𝚂𝚑𝚎𝚕𝚕@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙲𝚕𝚎𝚊𝚗 𝚄𝙰𝚃 (𝚙𝚛𝚎𝚜𝚎𝚛𝚟𝚎 𝚖𝚎𝚍𝚒𝚊 + 𝚕𝚘𝚐𝚜)'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚝𝚊𝚛𝚐𝚎𝚝𝚃𝚢𝚙𝚎: '𝚒𝚗𝚕𝚒𝚗𝚎'
𝚜𝚌𝚛𝚒𝚙𝚝: |
$𝚙𝚊𝚝𝚑="𝙳:\𝚆𝙴𝙱\𝙼𝚊𝚍𝚎𝚒𝚛𝚒𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊\𝚞𝚊𝚝.𝚖𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝚕𝚎𝚒𝚛𝚒𝚊.𝚙𝚝\𝚠𝚠𝚠𝚛𝚘𝚘𝚝"
$𝚙𝚛𝚎𝚜𝚎𝚛𝚟𝚎=@("𝚠𝚠𝚠𝚛𝚘𝚘𝚝\𝚖𝚎𝚍𝚒𝚊","𝚞𝚖𝚋𝚛𝚊𝚌𝚘\𝙻𝚘𝚐𝚜","𝙰𝚙𝚙_𝙾𝚏𝚏𝚕𝚒𝚗𝚎.𝚑𝚝𝚖")
$𝚝𝚎𝚖𝚙=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚎𝚗𝚟:𝚃𝙴𝙼𝙿 "𝚋𝚊𝚌𝚔𝚞𝚙_$(𝙶𝚎𝚝-𝙳𝚊𝚝𝚎 -𝙵𝚘𝚛𝚖𝚊𝚝 𝚢𝚢𝚢𝚢𝙼𝙼𝚍𝚍𝙷𝙷𝚖𝚖𝚜𝚜)"
𝙽𝚎𝚠-𝙸𝚝𝚎𝚖 $𝚝𝚎𝚖𝚙 -𝙸𝚝𝚎𝚖𝚃𝚢𝚙𝚎 𝙳𝚒𝚛𝚎𝚌𝚝𝚘𝚛𝚢 | 𝙾𝚞𝚝-𝙽𝚞𝚕𝚕
𝚏𝚘𝚛𝚎𝚊𝚌𝚑($𝚒𝚝𝚎𝚖 𝚒𝚗 $𝚙𝚛𝚎𝚜𝚎𝚛𝚟𝚎){
$𝚜𝚛𝚌=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚙𝚊𝚝𝚑 $𝚒𝚝𝚎𝚖
𝚒𝚏(𝚃𝚎𝚜𝚝-𝙿𝚊𝚝𝚑 $𝚜𝚛𝚌){
$𝚍𝚎𝚜𝚝=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚝𝚎𝚖𝚙 $𝚒𝚝𝚎𝚖
𝙽𝚎𝚠-𝙸𝚝𝚎𝚖 (𝚂𝚙𝚕𝚒𝚝-𝙿𝚊𝚝𝚑 $𝚍𝚎𝚜𝚝) -𝙸𝚝𝚎𝚖𝚃𝚢𝚙𝚎 𝙳𝚒𝚛𝚎𝚌𝚝𝚘𝚛𝚢 -𝙵𝚘𝚛𝚌𝚎 | 𝙾𝚞𝚝-𝙽𝚞𝚕𝚕
𝚛𝚘𝚋𝚘𝚌𝚘𝚙𝚢 $𝚜𝚛𝚌 $𝚍𝚎𝚜𝚝 /𝙴 | 𝙾𝚞𝚝-𝙽𝚞𝚕𝚕
}
}
𝙶𝚎𝚝-𝙲𝚑𝚒𝚕𝚍𝙸𝚝𝚎𝚖 $𝚙𝚊𝚝𝚑 | 𝙵𝚘𝚛𝙴𝚊𝚌𝚑-𝙾𝚋𝚓𝚎𝚌𝚝 {
𝚒𝚏($_.𝙽𝚊𝚖𝚎 -𝚗𝚎 "𝙰𝚙𝚙_𝙾𝚏𝚏𝚕𝚒𝚗𝚎.𝚑𝚝𝚖"){
𝚁𝚎𝚖𝚘𝚟𝚎-𝙸𝚝𝚎𝚖 $_.𝙵𝚞𝚕𝚕𝙽𝚊𝚖𝚎 -𝚁𝚎𝚌𝚞𝚛𝚜𝚎 -𝙵𝚘𝚛𝚌𝚎
}
}
𝚏𝚘𝚛𝚎𝚊𝚌𝚑($𝚒𝚝𝚎𝚖 𝚒𝚗 $𝚙𝚛𝚎𝚜𝚎𝚛𝚟𝚎){
$𝚜𝚛𝚌=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚝𝚎𝚖𝚙 $𝚒𝚝𝚎𝚖
𝚒𝚏(𝚃𝚎𝚜𝚝-𝙿𝚊𝚝𝚑 $𝚜𝚛𝚌){
$𝚍𝚎𝚜𝚝=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚙𝚊𝚝𝚑 $𝚒𝚝𝚎𝚖
𝙽𝚎𝚠-𝙸𝚝𝚎𝚖 (𝚂𝚙𝚕𝚒𝚝-𝙿𝚊𝚝𝚑 $𝚍𝚎𝚜𝚝) -𝙸𝚝𝚎𝚖𝚃𝚢𝚙𝚎 𝙳𝚒𝚛𝚎𝚌𝚝𝚘𝚛𝚢 -𝙵𝚘𝚛𝚌𝚎 | 𝙾𝚞𝚝-𝙽𝚞𝚕𝚕
𝚛𝚘𝚋𝚘𝚌𝚘𝚙𝚢 $𝚜𝚛𝚌 $𝚍𝚎𝚜𝚝 /𝙴 | 𝙾𝚞𝚝-𝙽𝚞𝚕𝚕
}
}
𝚁𝚎𝚖𝚘𝚟𝚎-𝙸𝚝𝚎𝚖 $𝚝𝚎𝚖𝚙 -𝚁𝚎𝚌𝚞𝚛𝚜𝚎 -𝙵𝚘𝚛𝚌𝚎
- 𝚝𝚊𝚜𝚔: 𝙲𝚘𝚙𝚢𝙵𝚒𝚕𝚎𝚜@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙲𝚘𝚙𝚢 𝙵𝚒𝚕𝚎𝚜'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚂𝚘𝚞𝚛𝚌𝚎𝙵𝚘𝚕𝚍𝚎𝚛: '$(𝙿𝚒𝚙𝚎𝚕𝚒𝚗𝚎.𝚆𝚘𝚛𝚔𝚜𝚙𝚊𝚌𝚎)/𝚍𝚛𝚘𝚙/𝚠𝚠𝚠'
𝙲𝚘𝚗𝚝𝚎𝚗𝚝𝚜: |
**
!𝚠𝚠𝚠𝚛𝚘𝚘𝚝/𝚖𝚎𝚍𝚒𝚊/**
!𝚞𝚖𝚋𝚛𝚊𝚌𝚘/𝙻𝚘𝚐𝚜/**
𝚃𝚊𝚛𝚐𝚎𝚝𝙵𝚘𝚕𝚍𝚎𝚛: '𝙳:\𝚆𝙴𝙱\𝙼𝚊𝚍𝚎𝚒𝚛𝚒𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊\𝚞𝚊𝚝.𝚖𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝚕𝚎𝚒𝚛𝚒𝚊.𝚙𝚝\𝚠𝚠𝚠𝚛𝚘𝚘𝚝'
𝙾𝚟𝚎𝚛𝚆𝚛𝚒𝚝𝚎: 𝚝𝚛𝚞𝚎
- 𝚝𝚊𝚜𝚔: 𝙿𝚘𝚠𝚎𝚛𝚂𝚑𝚎𝚕𝚕@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝚂𝚎𝚝 𝙰𝚂𝙿𝙽𝙴𝚃𝙲𝙾𝚁𝙴_𝙴𝙽𝚅𝙸𝚁𝙾𝙽𝙼𝙴𝙽𝚃'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚝𝚊𝚛𝚐𝚎𝚝𝚃𝚢𝚙𝚎: '𝚒𝚗𝚕𝚒𝚗𝚎'
𝚜𝚌𝚛𝚒𝚙𝚝: |
$𝚌𝚘𝚗𝚏𝚒𝚐="𝙳:\𝚆𝙴𝙱\𝙼𝚊𝚍𝚎𝚒𝚛𝚒𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊\𝚞𝚊𝚝.𝚖𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝚕𝚎𝚒𝚛𝚒𝚊.𝚙𝚝\𝚠𝚠𝚠𝚛𝚘𝚘𝚝\𝚠𝚎𝚋.𝚌𝚘𝚗𝚏𝚒𝚐"
[𝚡𝚖𝚕]$𝚡𝚖𝚕 = 𝙶𝚎𝚝-𝙲𝚘𝚗𝚝𝚎𝚗𝚝 $𝚌𝚘𝚗𝚏𝚒𝚐
$𝚗𝚘𝚍𝚎=$𝚡𝚖𝚕.𝚂𝚎𝚕𝚎𝚌𝚝𝚂𝚒𝚗𝚐𝚕𝚎𝙽𝚘𝚍𝚎("//𝚎𝚗𝚟𝚒𝚛𝚘𝚗𝚖𝚎𝚗𝚝𝚅𝚊𝚛𝚒𝚊𝚋𝚕𝚎[@𝚗𝚊𝚖𝚎='𝙰𝚂𝙿𝙽𝙴𝚃𝙲𝙾𝚁𝙴_𝙴𝙽𝚅𝙸𝚁𝙾𝙽𝙼𝙴𝙽𝚃']")
𝚒𝚏($𝚗𝚘𝚍𝚎 -𝚎𝚚 $𝚗𝚞𝚕𝚕){
$𝚎𝚗𝚟𝙽𝚘𝚍𝚎=$𝚡𝚖𝚕.𝚂𝚎𝚕𝚎𝚌𝚝𝚂𝚒𝚗𝚐𝚕𝚎𝙽𝚘𝚍𝚎("//𝚎𝚗𝚟𝚒𝚛𝚘𝚗𝚖𝚎𝚗𝚝𝚅𝚊𝚛𝚒𝚊𝚋𝚕𝚎𝚜")
$𝚗𝚎𝚠𝙽𝚘𝚍𝚎=$𝚡𝚖𝚕.𝙲𝚛𝚎𝚊𝚝𝚎𝙴𝚕𝚎𝚖𝚎𝚗𝚝("𝚎𝚗𝚟𝚒𝚛𝚘𝚗𝚖𝚎𝚗𝚝𝚅𝚊𝚛𝚒𝚊𝚋𝚕𝚎")
$𝚗𝚎𝚠𝙽𝚘𝚍𝚎.𝚂𝚎𝚝𝙰𝚝𝚝𝚛𝚒𝚋𝚞𝚝𝚎("𝚗𝚊𝚖𝚎","𝙰𝚂𝙿𝙽𝙴𝚃𝙲𝙾𝚁𝙴_𝙴𝙽𝚅𝙸𝚁𝙾𝙽𝙼𝙴𝙽𝚃")
$𝚗𝚎𝚠𝙽𝚘𝚍𝚎.𝚂𝚎𝚝𝙰𝚝𝚝𝚛𝚒𝚋𝚞𝚝𝚎("𝚟𝚊𝚕𝚞𝚎","𝚂𝚝𝚊𝚐𝚒𝚗𝚐")
$𝚎𝚗𝚟𝙽𝚘𝚍𝚎.𝙰𝚙𝚙𝚎𝚗𝚍𝙲𝚑𝚒𝚕𝚍($𝚗𝚎𝚠𝙽𝚘𝚍𝚎)
} 𝚎𝚕𝚜𝚎 {
$𝚗𝚘𝚍𝚎.𝚟𝚊𝚕𝚞𝚎="𝚂𝚝𝚊𝚐𝚒𝚗𝚐"
}
$𝚡𝚖𝚕.𝚂𝚊𝚟𝚎($𝚌𝚘𝚗𝚏𝚒𝚐)
- 𝚝𝚊𝚜𝚔: 𝙿𝚘𝚠𝚎𝚛𝚂𝚑𝚎𝚕𝚕@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝚂𝚎𝚝 𝙰𝙲𝙻𝚜'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚝𝚊𝚛𝚐𝚎𝚝𝚃𝚢𝚙𝚎: '𝚒𝚗𝚕𝚒𝚗𝚎'
𝚜𝚌𝚛𝚒𝚙𝚝: |
$𝚛𝚘𝚘𝚝="𝙳:\𝚆𝙴𝙱\𝙼𝚊𝚍𝚎𝚒𝚛𝚒𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊\𝚞𝚊𝚝.𝚖𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝚕𝚎𝚒𝚛𝚒𝚊.𝚙𝚝\𝚠𝚠𝚠𝚛𝚘𝚘𝚝"
$𝚊𝚙𝚙𝙿𝚘𝚘𝚕="𝙸𝙸𝚂 𝙰𝚙𝚙𝙿𝚘𝚘𝚕\𝚞𝚊𝚝.𝚖𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝚕𝚎𝚒𝚛𝚒𝚊.𝚙𝚝"
$𝚍𝚒𝚛𝚜=@("𝚞𝚖𝚋𝚛𝚊𝚌𝚘","𝙰𝚙𝚙_𝙿𝚕𝚞𝚐𝚒𝚗𝚜","𝚅𝚒𝚎𝚠𝚜","𝚠𝚠𝚠𝚛𝚘𝚘𝚝\𝚖𝚎𝚍𝚒𝚊","𝚠𝚠𝚠𝚛𝚘𝚘𝚝\𝚌𝚜𝚜","𝚠𝚠𝚠𝚛𝚘𝚘𝚝\𝚜𝚌𝚛𝚒𝚙𝚝𝚜","𝚠𝚠𝚠𝚛𝚘𝚘𝚝\𝚞𝚖𝚋𝚛𝚊𝚌𝚘")
𝚏𝚘𝚛𝚎𝚊𝚌𝚑($𝚍 𝚒𝚗 $𝚍𝚒𝚛𝚜){
$𝚏𝚞𝚕𝚕=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚛𝚘𝚘𝚝 $𝚍
𝚒𝚏(𝚃𝚎𝚜𝚝-𝙿𝚊𝚝𝚑 $𝚏𝚞𝚕𝚕){
𝚒𝚌𝚊𝚌𝚕𝚜 $𝚏𝚞𝚕𝚕 /𝚐𝚛𝚊𝚗𝚝 "$𝚊𝚙𝚙𝙿𝚘𝚘𝚕:(𝙾𝙸)(𝙲𝙸)(𝙼)" /𝚃
}
}
- 𝚝𝚊𝚜𝚔: 𝙿𝚘𝚠𝚎𝚛𝚂𝚑𝚎𝚕𝚕@𝟸
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙳𝚎𝚊𝚌𝚝𝚒𝚟𝚊𝚝𝚎 𝙰𝚙𝚙_𝙾𝚏𝚏𝚕𝚒𝚗𝚎'
𝚒𝚗𝚙𝚞𝚝𝚜:
𝚝𝚊𝚛𝚐𝚎𝚝𝚃𝚢𝚙𝚎: '𝚒𝚗𝚕𝚒𝚗𝚎'
𝚜𝚌𝚛𝚒𝚙𝚝: |
$𝚛𝚘𝚘𝚝="𝙳:\𝚆𝙴𝙱\𝙼𝚊𝚍𝚎𝚒𝚛𝚒𝚊𝚜𝙻𝚎𝚒𝚛𝚒𝚊\𝚞𝚊𝚝.𝚖𝚊𝚍𝚎𝚒𝚛𝚊𝚜𝚕𝚎𝚒𝚛𝚒𝚊.𝚙𝚝\𝚠𝚠𝚠𝚛𝚘𝚘𝚝"
$𝚝𝚎𝚖𝚙𝚕𝚊𝚝𝚎=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚛𝚘𝚘𝚝 "$(𝚘𝚏𝚏𝚕𝚒𝚗𝚎𝚃𝚎𝚖𝚙𝚕𝚊𝚝𝚎)"
$𝚘𝚏𝚏𝚕𝚒𝚗𝚎=𝙹𝚘𝚒𝚗-𝙿𝚊𝚝𝚑 $𝚛𝚘𝚘𝚝 "$(𝚘𝚏𝚏𝚕𝚒𝚗𝚎𝙵𝚒𝚕𝚎)"
𝚒𝚏(𝚃𝚎𝚜𝚝-𝙿𝚊𝚝𝚑 $𝚘𝚏𝚏𝚕𝚒𝚗𝚎){
𝚒𝚏(𝚃𝚎𝚜𝚝-𝙿𝚊𝚝𝚑 $𝚝𝚎𝚖𝚙𝚕𝚊𝚝𝚎){ 𝚁𝚎𝚖𝚘𝚟𝚎-𝙸𝚝𝚎𝚖 $𝚝𝚎𝚖𝚙𝚕𝚊𝚝𝚎 -𝙵𝚘𝚛𝚌𝚎 }
𝚁𝚎𝚗𝚊𝚖𝚎-𝙸𝚝𝚎𝚖 $𝚘𝚏𝚏𝚕𝚒𝚗𝚎 $𝚝𝚎𝚖𝚙𝚕𝚊𝚝𝚎 -𝙵𝚘𝚛𝚌𝚎
}
- 𝚜𝚝𝚊𝚐𝚎: 𝙳𝚎𝚙𝚕𝚘𝚢_𝙿𝚁𝙾𝙳
𝚍𝚒𝚜𝚙𝚕𝚊𝚢𝙽𝚊𝚖𝚎: '𝙳𝚎𝚙𝚕𝚘𝚢 𝚝𝚘 𝙿𝚛𝚘𝚍𝚞𝚌𝚝𝚒𝚘𝚗'
𝚍𝚎𝚙𝚎𝚗𝚍𝚜𝙾𝚗: 𝙳𝚎𝚙𝚕𝚘𝚢_𝚄𝙰𝚃
𝚌𝚘𝚗𝚍𝚒𝚝𝚒𝚘𝚗: 𝚜𝚞𝚌𝚌𝚎𝚎𝚍𝚎𝚍()
𝚓𝚘𝚋𝚜:
- 𝚍𝚎𝚙𝚕𝚘𝚢𝚖𝚎𝚗𝚝: 𝙿𝚛𝚘𝚍𝙳𝚎𝚙𝚕𝚘𝚢
𝚎𝚗𝚟𝚒𝚛𝚘𝚗𝚖𝚎𝚗𝚝:
𝚗𝚊𝚖𝚎: '𝙿𝚁𝙾𝙳-𝙳𝙱𝙻𝟶𝟷'
𝚛𝚎𝚜𝚘𝚞𝚛𝚌𝚎𝚃𝚢𝚙𝚎: 𝚅𝚒𝚛𝚝𝚞𝚊𝚕𝙼𝚊𝚌𝚑𝚒𝚗𝚎
𝚛𝚞𝚗𝙾𝚗𝚌𝚎:
𝚍𝚎𝚙𝚕𝚘𝚢:
𝚜𝚝𝚎𝚙𝚜:
- 𝚌𝚑𝚎𝚌𝚔𝚘𝚞𝚝: 𝚗𝚘𝚗𝚎
- 𝚍𝚘𝚠𝚗𝚕𝚘𝚊𝚍: 𝚌𝚞𝚛𝚛𝚎𝚗𝚝
𝚊𝚛𝚝𝚒𝚏𝚊𝚌𝚝: 𝚍𝚛𝚘𝚙
# 𝚂𝙰𝙼𝙴 𝙻𝙾𝙶𝙸𝙲 𝙰𝚂 𝚄𝙰𝚃, 𝚆𝙸𝚃𝙷 𝙿𝚁𝙾𝙳𝚄𝙲𝚃𝙸𝙾𝙽 𝙿𝙰𝚃𝙷𝚂
If you're working on a new CI/CD setup or trying to improve the one you already have, and things start to get a bit messy once you add multiple servers, media sync, security rules or more advanced workflows, you're definitely not alone. We help teams with this every day.
Our developers have built and deployed Umbraco projects of all sizes: small business sites, large enterprise platforms, public sector projects and everything in between. If you’d like someone experienced to look at your setup, or simply want advice on the best approach, we’re happy to help.
If you want to review your deployment process or plan your next steps, feel free to reach out. No pressure or commitments. Just a friendly chat about what you're trying to achieve.
If you're facing challenges with load balancing, scaling, deployment automation or anything around Umbraco and DevOps, feel free to reach out. We might be the right team to support you, and our clients usually find our pricing very competitive.
--
Written by Marco Teodoro
Founder & CEO, Double Shore