How to Set Up ADO for Startups

How to Set Up ADO for Startups

Set up Azure DevOps for safer CI/CD, approvals, access, and rollbacks.

Arthur Azrieli
Book Icon - Software Webflow Template
 min read

Azure DevOps can get messy fast when a team is under pressure to ship. Someone creates a pipeline by hand, another person grants admin access so a release can go out, secrets land in plain variables, and nobody knows how to promote the same build to production safely.

A good Azure DevOps (ADO) setup does not need to be huge. It needs to let a developer merge code, run continuous integration (CI), deploy to a non-production environment, promote the same build to production with approval, and trace failures quickly when something breaks.

This guide assumes you are a startup or growth-stage team using, or considering, Azure DevOps for source control, pipelines, release controls, and cloud deployments. The goal is a setup that is safe enough for production without creating a process that slows every engineer down.

Start with the operating model, not the pipeline YAML

Before you create projects, agents, service connections, and environments, decide how work should move through your system. Most ADO problems come from unclear ownership rather than missing features.

For a small team, use a simple model:

  • Developers own application code and application deployment health. They should be able to see pipeline logs, deployment history, and service metrics.
  • Platform or infrastructure owners define shared deployment patterns. This may be one founding engineer at seed stage, or a small platform team later.
  • Production changes require review and approval. Approval should be lightweight, but it should exist.
  • Infrastructure changes use infrastructure as code. Avoid one-off portal changes that no one can reproduce.
  • Rollback is part of the deployment design. Do not wait for the first incident to decide how to revert.

At an early stage, one ADO project is usually enough. Split by service only when you have a real ownership boundary, not because the UI looks cleaner. A common starting point is:

  • One project for engineering. Example: product-platform.
  • One repository per deployable service if services are owned and released separately.
  • One repository for shared infrastructure if Terraform, Bicep, Helm charts, or shared modules need their own lifecycle.
  • Shared pipeline templates when the second or third service repeats the same build and deploy steps.

If you are still choosing between Azure DevOps, GitHub Actions, GitLab CI, CircleCI, or another platform, step back and compare the tradeoffs before committing your deployment process to one vendor. This breakdown of how to choose the right DevOps tools for your team can help you avoid selecting a tool only because one engineer used it at a previous company.

Set up projects, repos, and permissions with least privilege

ADO permissions tend to drift when teams are moving quickly. A developer gets Project Administrator access to fix one release. A contractor keeps broad permissions after the engagement ends. A production service connection can be used by every pipeline in the project. These shortcuts work until they do not.

Start with a few clear groups:

  • Project Administrators: very small group, ideally engineering leadership and platform owners.
  • Contributors: engineers who can push branches, open pull requests, and view pipeline results.
  • Release Approvers: people allowed to approve production deployments.
  • Readers: people who need visibility but should not change code or pipelines.
  • Service Accounts: identities used by automation, never shared by humans.

Use branch policies on your default branch. For most startups, the following is enough:

  • Require at least one reviewer for pull requests.
  • Require a successful build validation pipeline.
  • Block direct pushes to main.
  • Reset approvals when new changes are pushed.
  • Require owners for sensitive paths, such as /infra, /helm, or /pipelines.

Screenshot to add: ADO Project Settings showing the main security groups, with Project Administrators limited to a small number of users.

Screenshot to add: Branch policies for main, showing required reviewers and build validation.

Do not give broad admin access because a pipeline is failing. Fix the identity, permission, or service connection that the pipeline actually needs. This takes longer the first time, but it prevents a permissions model where every engineer can accidentally deploy or modify production infrastructure.

Create service connections and secret handling you can defend later

Service connections are one of the most important parts of an ADO setup. They decide what your pipelines can touch in Azure, Kubernetes, container registries, and other systems.

Use separate service connections for non-production and production. For example:

  • sc-azure-nonprod-deploy
  • sc-azure-prod-deploy
  • sc-acr-push for pushing container images
  • sc-aks-nonprod and sc-aks-prod if you deploy to Azure Kubernetes Service

Restrict each service connection to the pipelines that need it. In ADO, avoid enabling access for all pipelines unless you are dealing with a low-risk sandbox. Production service connections should require explicit authorization.

Screenshot to add: A production Azure Resource Manager service connection with restricted pipeline permissions and a clear naming convention.

For secrets, avoid plain pipeline variables for anything sensitive. Use one of these patterns instead:

  • Azure Key Vault linked variable groups for application secrets used at deploy time.
  • Managed identities where supported, so workloads can access cloud resources without long-lived secrets.
  • Secret variables only for limited cases, and never as the default secret strategy.

Common mistakes include storing production database passwords in YAML, copying secrets into variable groups without access review, and using the same credentials for non-production and production. These choices usually happen because the first deploy needed to work quickly. Clean them up before more services depend on the pattern.

A simple secret rule works well: developers can deploy without seeing production secrets. They can trigger the deployment, review logs with sensitive values masked, and debug through application telemetry and configuration history.

Build a minimal CI/CD pipeline that promotes the same artifact

A startup pipeline should be boring. Build once, test once, publish an artifact or container image, deploy that same version to non-production, then promote it to production with approval.

Avoid separate production builds. If production uses a different build than staging, you are no longer promoting what you tested. You are hoping the second build behaves the same way.

Here is a minimal Azure Pipelines YAML example for a containerized service. It builds on pull requests and main, pushes an image, deploys to a non-production environment, then waits for production approval through an ADO Environment check.

trigger:
  branches:
    include:
      - main

pr:
  branches:
    include:
      - main

variables:
  imageName: my-api
  dockerfilePath: Dockerfile
  containerRegistry: myregistry.azurecr.io

stages:
  - stage: CI
    displayName: Build and test
    jobs:
      - job: build_test
        displayName: Build and test
        pool:
          vmImage: ubuntu-latest
        steps:
          - checkout: self

          - script: |
              npm ci
              npm test
            displayName: Run tests

          - task: Docker@2
            displayName: Build and push image
            inputs:
              command: buildAndPush
              repository: $(imageName)
              dockerfile: $(dockerfilePath)
              containerRegistry: sc-acr-push
              tags: |
                $(Build.SourceVersion)

  - stage: Deploy_NonProd
    displayName: Deploy to non-production
    dependsOn: CI
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: deploy_nonprod
        displayName: Deploy non-production
        environment: nonprod
        strategy:
          runOnce:
            deploy:
              steps:
                - script: |
                    echo "Deploy $(containerRegistry)/$(imageName):$(Build.SourceVersion) to nonprod"
                    # Replace with Helm, kubectl, Azure Web App, or your deploy command
                  displayName: Deploy application

  - stage: Deploy_Prod
    displayName: Deploy to production
    dependsOn: Deploy_NonProd
    condition: succeeded()
    jobs:
      - deployment: deploy_prod
        displayName: Deploy production
        environment: production
        strategy:
          runOnce:
            deploy:
              steps:
                - script: |
                    echo "Deploy $(containerRegistry)/$(imageName):$(Build.SourceVersion) to production"
                    # Replace with the same deployment mechanism used in nonprod
                  displayName: Deploy application

This example is intentionally small. In a real service, you may add linting, security scanning, database migration checks, smoke tests, and deployment templates. Add those when they reduce real risk. Do not create a 900-line pipeline on day one because a larger company had one.

Screenshot to add: Pipeline run summary showing separate stages for CI, non-production deployment, and production deployment.

If you deploy to Kubernetes, keep cluster upgrades and application deployment concerns separate. Application pipelines should deploy workloads. Cluster lifecycle changes should move through infrastructure pipelines with their own review path. If Kubernetes is part of your stack, these practical tips for Kubernetes upgrades for startups are useful when you start formalizing cluster operations.

Use environments, approvals, and checks without creating release theater

ADO Environments give you deployment history, approvals, and checks. Use them for actual runtime targets, such as nonprod, staging, and production.

For non-production, keep the process fast. A successful merge to main should deploy automatically unless your team has a strong reason to pause. Developers need rapid feedback before production.

For production, add a simple approval check:

  • Require one approver from the Release Approvers group.
  • Prevent the person who triggered the run from approving when your team size allows it.
  • Add business-hour restrictions only if your team can support them without blocking urgent fixes.
  • Keep approval comments short and useful, such as the ticket number, risk note, or rollback plan.

Screenshot to add: The production Environment approvals and checks page showing one required approver group.

Do not treat approvals as a replacement for tests, review, and observability. If every production deploy needs three people in a meeting, your process is too heavy. If anyone can deploy to production from any branch, your process is too loose. The middle ground is a tested build, a visible deployment record, and a production approval by someone accountable.

Also decide what happens when a deployment fails. At minimum, each service should have:

  • A rollback command or documented procedure. Example: redeploy the previous container image tag.
  • A migration policy. Database changes should be backward compatible when possible.
  • A smoke test. Confirm the service starts and responds after deployment.
  • A clear owner. The person or team that responds when the deployment breaks.

A rollback plan does not need to be complex. For many services, the first version can be: find the last successful production deployment in ADO, redeploy that image tag, and verify the health endpoint. Write it down in the repository so the person on call is not guessing at 2 a.m.

Make failures easy to trace

A pipeline that fails with no useful signal wastes engineering time. A deployment that succeeds while the service is broken is worse. Your ADO setup should make it easy to answer four questions:

  1. What commit is running in each environment?
  2. Who approved the production deployment?
  3. What changed in configuration or infrastructure?
  4. Where do logs, metrics, traces, and alerts live?

Use consistent versioning. For container images, tag with the commit SHA, such as $(Build.SourceVersion). You can also add a shorter human-readable tag, but the commit SHA should be enough to map production back to source.

Add deployment annotations where your observability tooling supports them. If an incident starts five minutes after a deployment, the on-call engineer should see the deployment event near the error spike, latency change, or restart count.

Keep logs and metrics linked from the service README or runbook. A practical service README includes:

  • Pipeline link
  • Non-production and production environment names
  • Dashboard links
  • Log query examples
  • Rollback steps
  • Known deployment risks

Alerting also needs restraint. If every failed non-production deployment pages the team, people will ignore alerts. Page on production user impact, failed production deployment requiring action, or capacity issues that need fast response. Send lower-priority signals to Slack, Teams, or the work tracker. If your team already gets too many noisy alerts, this guide on how to handle alert fatigue can help you clean up the signal before adding more checks.

Avoid the setup mistakes that slow startups later

Most ADO setups fail in predictable ways. You can avoid many of them with a few early decisions.

  • Do not overbuild process too early. A five-person team does not need the same release board as a 500-person company. Start with pull request checks, non-production deploys, production approvals, and rollback documentation.
  • Do not give everyone admin access. Use groups, service connections, and scoped permissions. Review access when people change roles or leave.
  • Do not store secrets casually. Use Key Vault or an equivalent secret manager. Keep production secrets out of YAML and plain variables.
  • Do not mix app and infrastructure ownership without rules. Application engineers can own service deploys. Infrastructure changes should use reviewed infrastructure as code.
  • Do not skip rollback planning. Every production service needs a known path back to the last good version.
  • Do not create one-off pipelines for every service. Once two services need the same pattern, create a shared template or documented standard.

As the team grows, decide who owns the platform work. This may start as a rotating responsibility, then become a named owner, then become a small platform or DevOps function. The shape depends on your team size, incident load, and deployment frequency. If you are at that point, this guide on how to build a DevOps team can help you choose a structure without hiring too early or too late.

Takeaway

A solid ADO setup for a startup should be simple, repeatable, and hard to misuse. Developers should merge code, see CI results, deploy to non-production, promote the same artifact to production with approval, and trace failures without hunting through private notes or tribal knowledge.

Start with one clean path for one service. Add branch policies, scoped service connections, secret management, environments, approvals, rollback steps, and basic observability links. Then turn that path into a reusable pattern before every team invents its own.

If your current setup already has fragile pipelines, unclear permissions, or production deployments that depend on one person, it is worth fixing before the next scaling push. You can also get a second set of eyes through a DevOps setup for production consultation if you want help pressure-testing the path before more services depend on it.