Skip to content

Install on Azure

Admin guide

Greenlight installs into an Azure subscription you control. Plan on about 15–20 minutes for infrastructure provisioning and another 15–20 minutes for the install wizard.

Azure Supported GCP Supported AWS Supported

This page covers Azure. See Install on GCP or Install on AWS for the equivalent deployment in those clouds.

Your Shift implementation contact delivers the approved release with a private, versioned ARM template URL and its release instructions. The release carries the compiled Bicep deployment, pinned Kubernetes assets, release metadata, and access to the Greenlight container images for that version. The public docs describe the resources and permissions without publishing the template URL or image access details.

  • An Azure subscription and a dedicated Greenlight resource group. You do not need to know which region to use, or whether it will work: the installer lists the regions your subscription can use and checks the one you pick before anything is created. Some subscription types — sponsorships and certain enterprise agreements — restrict which regions and VM sizes they may use, so the answer differs between two subscriptions looking at the same region.
  • A provisioning identity with Owner, or Contributor plus Role Based Access Control Administrator, on that resource group. Resource-provider registration is a subscription-level action; an identity without /register/action cannot complete a first install on a fresh subscription.
  • A non-overlapping RFC 1918 range if the Greenlight VNet will peer with an existing network.
  • A domain where you can delegate one subdomain, such as greenlight.contoso.com, to Azure DNS.
  • An IT administrator who can configure the OIDC identity provider, delegate the DNS subdomain, install the Greenlight GitHub App, and become the initial Greenlight org_admin.
  • The tools on the machine you install from, at the versions listed in the private package README. The installer checks all of them before it asks for anything and tells you how to install whatever is missing, on one install line for your platform.
    • macOS or Linux: az, kubectl, helm, jq, and curl, plus bash 4.3 or newer. On macOS, /bin/bash is 3.2 and too old — brew install bash and run the installer with that one. If az, helm, kubectl, jq, or curl are missing too, that same message names them on one brew install line.
    • Windows 10 or 11: PowerShell 7.2 or newer (winget install Microsoft.PowerShell), az, kubectl, helm, and curl (which Windows ships). No WSL, Docker, or virtualization is needed, and the installer itself needs no administrator rights — only installing those tools may. Windows ships the older Windows PowerShell 5.1 as powershell; run the installer with pwsh. Hosts whose policy blocks running scripts — group policy enforcing AllSigned, or AppLocker/WDAC script rules — are not supported by this path; ask your Windows administrators for an exception or install from macOS or Linux.

Fresh subscriptions usually have most resource providers unregistered. The installer registers the namespaces Greenlight needs — including Microsoft.Compute, which the node-size check reads — before it asks Azure which databases and VM sizes the chosen region offers, then names that subscription change on the review screen. No resource group or cluster is created until you confirm. --plan-only performs the same registration (and the same resource-group create it already did). An identity that cannot register providers is told the az provider register --namespace command to run instead.

Everything below is created inside the dedicated Greenlight resource group.

Area Resources
Network A VNet with separate AKS system, AKS app, control-plane database, and app-database subnets, plus private DNS
Compute One AKS cluster with a dedicated system node pool and an autoscaling app node pool
Databases Two private PostgreSQL Flexible Servers: one for Greenlight and one for app-owned logical databases
Storage One Storage account with public blob access disabled for app objects, app assets, and platform storage
Registry One Azure Container Registry for Greenlight and app images
Secrets One Key Vault with a private endpoint for platform, integration, and app secrets
DNS and ingress One Azure DNS zone, a public ingress address, Traefik, and cert-manager
Identity Dedicated managed identities and federated workload-identity bindings for the control plane, certificates, data access, and deployed apps

The public entry points are the Greenlight/app ingress and the authenticated AKS API. PostgreSQL and Key Vault stay on private networking, storage disallows public blob access, and workloads use managed identity instead of stored cloud credentials. Delete locks protect the cluster, both database servers, storage, and the Key Vault, so nothing automated — and no accidental console action — can remove them without a deliberate unlock first.

The deployment template carries a Microsoft partner tracking ID, so that when you deploy it Microsoft can associate the Azure resources it creates with Shift as the publisher of the software running on them. The ID is nothing but the name of an empty deployment step: it provisions no resources, adds nothing to your bill, and reads nothing out of your subscription or your Greenlight installation. Microsoft collects and governs that information under its own privacy policies, and does not report your consumption back to Shift.

Deleting the Microsoft.Resources/deployments resource whose name begins with pid- from deployment/azure/main.json before an install leaves that run unattributed, and changes nothing else about the installation. It is not a durable opt-out on its own: the updater applies each release’s own template, which carries the ID again. If your organization needs to be excluded from this attribution permanently, contact Shift.

Greenlight selects and manages the infrastructure profile in the versioned deployment package. The values below describe the standard installation footprint for cost and quota review; they are not settings the installing administrator chooses or maintains.

Resource Default
AKS system node pool 1 × Standard_D4s_v5
AKS app node pool Standard_D4s_v5, autoscale 0–3
PostgreSQL control plane Flexible Server, B2ms
PostgreSQL app databases Flexible Server, B2ms
Key Vault Standard
Container Registry Basic

Greenlight manages workload autoscaling within this profile and delivers capacity changes through versioned releases. Do not edit node pools or database SKUs directly; the next Greenlight infrastructure reconcile treats the versioned deployment profile as the source of truth.

Some subscriptions restrict which VM sizes a region allows. When the default node size is not one of them, the installer says so and offers the sizes that region does have with the same 4 vCPU / 16 GiB shape — the same shape, not merely a bigger one — so you can pick one and carry on. A scripted run instead names the flag to pass — --control-node-size or --app-node-size, whichever it rejected — and stops before anything is created.

Identity Access Why
Provisioning identity Owner, or Contributor + Role Based Access Control Administrator, on the Greenlight resource group Creates resources and role assignments
Greenlight runner identity Resource-scoped access to AKS, PostgreSQL, Key Vault, ACR, Storage, and the delegated DNS zone Operates the control plane and provisions app resources
cert-manager identity DNS Zone Contributor on the delegated zone Completes DNS-01 certificate challenges
Connected-data identity No broad Azure resource access by default Receives access inside a connected database only when a DBA grants it
Updater identity Contributor + Role Based Access Control Administrator on the Greenlight resource group Applies Greenlight releases inside the maintenance window

The installer creates the runtime identities and their bindings. Your team does not create or distribute long-lived cloud keys.

The installing administrator supplies only values specific to the customer’s environment:

Input Required Purpose
Azure CLI session Yes Selects the target tenant and subscription
--resource-group No The one resource group Greenlight owns. Created if it does not exist; defaults to rg-greenlight
--location Yes Azure region where Greenlight resources are created. Omit it in an interactive run and the installer offers the regions your subscription lists
--control-plane-host Yes Delegated DNS zone and final Greenlight hostname
--certificate-contact No Contact address on the certificate issuer. Derived from your host name when omitted
--network-range Only when needed Replaces the release default when it overlaps a peered network
--key-vault-name Recovery only Supplies a new globally unique name when Azure still reserves the default Key Vault name from a deleted install

Every input above has a matching prompt: run ./install.sh with no arguments and it explains and asks for each one, so the flags are only needed for scripted runs — which must also pass --non-interactive: it requires every input up front, stops rather than prompting when one is missing, and is the only thing that skips the two confirmations. A run with no terminal that omits it stops at the first confirmation rather than proceeding on its own.

Resource names, VM sizes, database SKUs, Kubernetes versions, and Greenlight image digests are derived or pinned by the release. They are not install inputs.

Authenticate the Azure CLI, select the intended subscription, extract the release archive, and run the installer inside it:

Terminal window
az login --tenant <tenant-id>
az account set --subscription <subscription-id>
unzip greenlight-azure.zip
cd greenlight-azure-v<version>
./install.sh

On Windows, from PowerShell 7:

Terminal window
az login --tenant <tenant-id>
az account set --subscription <subscription-id>
Expand-Archive greenlight-azure.zip -DestinationPath .
Set-Location greenlight-azure-v<version>
pwsh -File .\install.ps1

install.ps1 takes the same flags as install.sh, asks the same questions, and records the same settings, so a Windows first install and a later rerun from macOS or the automatic updater are one installation. If Windows refuses to run it because it is not signed — files extracted by Explorer from a downloaded zip carry a download marker that the default RemoteSigned policy stops — run Get-ChildItem -Recurse | Unblock-File in that folder first, or start it as pwsh -ExecutionPolicy Bypass -File .\install.ps1, which applies to that one process only.

Run it with no arguments. The installer explains and asks for the resource group, region, and host name, and offers the standard private network 10.240.0.0/16 as a yes/no — that range cannot be changed after install and cannot be peered if it overlaps a network you will connect later; only a “no” asks for a different range. For the region it shows a numbered list, grouped the way Azure groups its regions, of the regions this subscription can use; enter a number, or type a region name if you already know which one you want. The chosen region is checked before anything is created; if it cannot host this release’s database, the installer says which region and why, and asks you to pick another — nothing has been created, so a region that does not work costs you one more question. Working out that list changes nothing in your subscription, and if it cannot be worked out the installer says so and asks you to type a name instead. To answer up front instead, pass the flags from the table above. You never write or edit an ARM parameter file; the installer generates one from your answers.

  1. Validate the target subscription. Confirm the active Azure tenant and subscription before running the command. Before creating the resource group, the installer checks every required tool and version, verifies it can pull the release’s pinned public images, registers any missing resource providers so ARM will answer, and checks that the region offers PostgreSQL 16 with a database size this release can use and each requested node size for this subscription. If something is missing it names the one setting it rejected, and it distinguishes “this region does not offer that” from “the check itself could not run” — so a sign-in that has expired never sends you looking for another region. An interactive run asks you for another region or another node size rather than stopping; a scripted run stops, having nobody to ask.

  2. Confirm twice. The installer shows a review screen — subscription, resource group, region, host name, networking, node sizes, and the exact image digests — and waits for you to confirm before the resource group is touched. It then shows what the deployment would change and asks you to confirm yes or no. The deployment that runs uses the same template and the same settings it previewed.

  3. Let the deployment complete. It creates the Azure resources, obtains AKS credentials, then applies the release-pinned Gateway API, Traefik, cert-manager, and Greenlight Helm releases. It prints a phase name and the matching guide section before each operation, and for each step that can run for minutes without output it shows what it is waiting for and how long it has been waiting — so a long silence is never ambiguous. It can be rerun safely after a partial failure.

    Running it from a pipeline, or prefer plain output? Pass --no-color (or set NO_COLOR) for text only, with no colour and no progress indicator. Redirected output is already plain. Set GREENLIGHT_INSTALL_VERBOSE=1 to see every command’s full output instead of a progress indicator.

  4. Open the install wizard. The install prints Finish setup in the browser: and a temporary URL that works before your final DNS name and certificate are ready. The wizard then shows the Azure DNS nameservers to delegate.

  5. Delegate the zone. Create the NS records shown by the wizard in your parent DNS. Greenlight creates the control-plane, wildcard app, and certificate-challenge records inside the delegated zone, then checks on a repeating timer until delegation, the certificate, and HTTPS are live — nothing to click while you wait.

  6. Finish the wizard. Configure OIDC, test sign-in, install the GitHub App, bootstrap the plugin marketplace, name the administrator we should contact about your trial and subscription (changed later in Settings → Organization), and confirm the organization and initial administrator.

  7. Verify the installation. Open the final HTTPS URL and run the installation-health checks.

If Azure still holds a soft-deleted Key Vault from a previous install under the same name, the preview stops before creating Greenlight resources and tells you to rerun with a new --key-vault-name. If the preview fails for another reason after creating a new resource group, its failure summary reports that group instead of claiming nothing changed.

Installation health verifies that:

  1. The final URL resolves over HTTPS.
  2. SSO resolves to a real IdP user with the required role mapping.
  3. The GitHub App can reach at least one repository.
  4. A plugin marketplace repository has its initial generated commit.
  5. The policy bundle and audit log are active.
  6. A test app can register and pass the policy check.

The deployment is idempotent: rerunning the same version reconciles the existing installation.

Greenlight upgrades through a customer-owned updater inside the same Azure boundary. It applies the versioned Bicep and Helm release using customer-controlled permissions. See Upgrades.

Every successful install records its settings inside the cluster, and a rerun reads them back so you only supply what is changing. An installation created before the installer recorded settings — or whose first run stopped before the final recording step — has no such record, so a rerun stops rather than guess values that would reshape live infrastructure. Adopt it once:

Terminal window
./install.sh --adopt-existing

Adoption reads the installation’s current settings from the deployment and the cluster, shows you what it found together with a change preview proving nothing would change, and records it. Names the installation was created with — a pinned cluster name, pinned database server names, a recovered Key Vault name, the deployment name it was deployed under — are carried over automatically, so later runs address the same resources rather than deriving new ones. If the preview would create or delete anything, an unattended run stops without recording; at a terminal you see the counts and decide. From then on, reruns behave like any other upgrade.

An installation created under a deployment name other than the default is adopted with that name:

Terminal window
./install.sh --adopt-existing --deployment-name <name>