Table of Contents
- Working with Azure DevOps: Create a Project, Import Code, and Set the Default Branch π
- Create and Connect to a Linux Virtual Machine in Azure βοΈπ§
- π Setting Up a Self-Hosted Linux Agent for Azure DevOps
- π Azure DevOps Pipeline: Build & Push Docker Image to Azure Container Registry
Working with Azure DevOps: Create a Project, Import Code, and Set the Default Branch π
In this section, weβll go step-by-step through the very first things you usually do after signing into Azure DevOps:
- Create a new project inside your organization
- Import an existing GitHub repository
- Configure the correct default branch
These steps are extremely important because they set up the foundation of your DevOps workflow.
If the project is not organized properly β or the wrong branch is default β pipelines, pull requests, and deployments can behave incorrectly later.
π‘ Key ideas to remember:
- Organization = your company or workspace container
- Project = a product/app inside the organization
- Repository = the source code for that project
- Default branch = the branch Azure DevOps uses for PRs, builds, and releases
Video Explanation
1οΈβ£ Create a Project in Azure DevOps
Before you can store code or run pipelines, you need a project.
A project acts as a workspace that contains:
- Boards (work items)
- Repos (code)
- Pipelines (CI/CD)
- Test plans
- Artifacts
Think of it as a folder that holds everything related to a single application.
Steps
- Open https://dev.azure.com
- Create or select an Organization
- Click New Project
- Enter:
- Project Name
- Description
- Set visibility β Private (recommended for learning & security)
- Click Create
After creation, the URL will update to include:
dev.azure.com/{organization-name}/{project-name}
π At this point your project is empty β no code yet β which weβll fix in the next step.
2οΈβ£ Import a Public GitHub Repository into Azure DevOps π¦
Instead of starting from scratch, you can import an existing repository.
Azure DevOps will copy the entire Git history β commits, branches, and files.
This is useful when:
- Migrating from GitHub to Azure DevOps
- Testing pipelines on a sample app
- Centralizing company code
Because the repository is public, authentication is not required.
Steps
- Go to Repos (left sidebar)
- Click Import repository
- Copy the GitHub repository HTTPS URL
- Paste the URL into Azure DevOps
- Click Import
β³ Azure DevOps now clones the repository internally.
What gets imported?
- Full commit history
- All branches
- Folder structure
- README and files
β οΈ Note:
Private repositories require authentication (PAT or credentials).
Public repositories do not.
3οΈβ£ Set main as the Default Branch πΏ
After importing, Azure DevOps automatically picks the default branch alphabetically β not logically.
That means:
dev β might become default β
main β what we actually want β
This matters because the default branch is used for:
- Pull request targets
- Pipeline triggers
- Release deployments
If itβs wrong, automation breaks later.
Steps
- Open Repos β Branches
- Find the main branch
- Click the three dots (β―) beside it
- Select Set as default branch
You will now see the main branch marked as default.
βοΈ What You Have Achieved
| Task | Purpose |
|---|---|
| Created project | Workspace for DevOps workflow |
| Imported repository | Added real application code |
| Set default branch | Ensures correct CI/CD behavior |
Create and Connect to a Linux Virtual Machine in Azure βοΈπ§
In this section, weβll set up a Linux virtual machine inside Azure and connect to it securely using SSH.
This machine will later act as a self-hosted agent for running DevOps pipelines β meaning builds and deployments will run on your own server instead of Microsoft-hosted agents.
Before starting, hereβs what matters most:
π Why we create this VM
- Run build & deployment jobs
- Install custom tools (Docker, kubectl, etc.)
- Control environment versions
- Practice real production-like DevOps setups
π How we connect
- Using SSH (secure remote login)
- With a key pair (.pem file)
- Over port 22
π‘ Important: Always keep the downloaded key safe β losing it means losing access to the VM.
Video Explanation
1οΈβ£ Creating a Linux VM in Azure Portal
Weβll create an Ubuntu server that Azure DevOps can later use as an agent machine.
A virtual machine automatically comes with supporting resources:
- Network interface
- Public IP
- Disk storage
- Security rules
Azure creates these together so the VM can be accessed over the internet.
Steps
- Open Azure Portal
- Click Create a resource β Virtual Machine
- Configure the basics:
| Setting | Example Value |
|---|---|
| Resource Group | Create new (e.g., Test RG) |
| VM Name | Any name (e.g., my-vm-azure) |
| Image | Ubuntu Server 22.04 |
| Username | azureuser |
Configure Authentication π
- Choose SSH Public Key
- Generate new key pair
- Give the key a name
Azure will download a .pem file β keep it safe!
This key is your password replacement.
Networking Setup π
To allow remote login:
- Allow inbound port β SSH (22)
Without this, connection will fail.
Create the VM
- Review settings
- Click Create
- Download the private key file (.pem)
After deployment completes, open the VM resource and note:
- Public IP address
- Resource group
- Networking details
Your Linux machine is now live π
2οΈβ£ Connecting to the Linux VM (SSH Login)
Now we log into the server remotely from your computer.
You can use:
- PowerShell (Windows)
- Terminal (Mac/Linux)
- Windows Terminal
Step 1: Go to Key Location
Open terminal and navigate to the folder containing the .pem file.
Example:
cd Downloads
Step 2: Run SSH Command
Use this format:
ssh -i yourkey.pem azureuser@PUBLIC_IP
Example:
ssh -i mykey.pem azureuser@20.55.xxx.xxx
If successful, your prompt changes to:
azureuser@vm-name:~$
You are now inside the Azure Linux machine π₯οΈ
Fix Permission Error (Very Common β οΈ)
You may see:
“Permissions are too open”
SSH blocks insecure keys.
Fix (Windows PowerShell)
Restrict file access so only you can read it.
Fix Windows SSH key permission:
icacls <key> /inheritance:r
icacls <key> /grant:r "$($env:USERNAME):(R)"
icacls <key> /remove "Authenticated Users" "BUILTIN\Users" "Everyone"
Login:
ssh -i <key> azureuser@<public-ip>
After fixing permissions, run the SSH command again.
βοΈ What You Achieved
| Task | Result |
|---|---|
| Created Linux VM | Cloud server ready |
| Generated SSH key | Secure authentication |
| Opened port 22 | Remote connectivity |
| Logged into VM | Ready for DevOps agent setup |
You now have your own cloud machine ready to install tools and run CI/CD pipelines π
π Setting Up a Self-Hosted Linux Agent for Azure DevOps
In this section, weβll configure a Linux Virtual Machine (VM) to act as a self-hosted agent for Azure DevOps pipelines. Instead of using Microsoft-hosted agents, your own VM will execute pipeline jobs β giving you more control, flexibility, and customization options.
Hereβs what weβll cover:
- π³ Installing and verifying Docker
- π§° Installing additional utilities (if required)
- π Generating a Personal Access Token (PAT)
- π Creating an Agent Pool
- π Connecting the VM to Azure DevOps
- βΆ Running the self-hosted agent
Video Explanation
By the end of this section, your Linux VM will be fully ready to execute CI/CD pipeline jobs π―
π³ Install and Configure Docker
Docker is commonly required in DevOps pipelines, especially for container-based builds and deployments.
Since we are logged in as azureuser, we must grant this user permission to run Docker commands without sudo.
πΉ Grant Docker Permissions
Run:
sudo usermod -aG docker azureuser
This adds the user to the Docker group.
π Restart Docker Service
sudo systemctl restart docker
Restarting ensures permission changes take effect.
πͺ Log Out and Log Back In
Exit the VM session:
exit
Then reconnect using your SSH command. This refreshes group membership.
β Verify Docker Installation
To confirm everything works correctly, pull the hello-world image:
docker pull hello-world
If the image downloads successfully:
- β Docker is installed
- β The service is running
- β The user has proper permissions
Your Docker setup is now complete π
π§° Install Additional Utilities (If Required)
Because this is a self-hosted VM, we are responsible for maintaining it.
Pipelines may require additional tools such as:
unzipwgetgit- language runtimes (Node, Java, etc.)
Even if your current project doesnβt require them, itβs important to understand that future pipelines might.
π¦ Example: Install unzip
sudo apt install unzip -y
π‘ Tip: Only install what your pipeline needs. Self-hosted agents provide flexibility β but also require proper maintenance.
π Connect the Linux VM to Azure DevOps
Now weβll connect the VM so that it can execute pipeline jobs from:
- Azure DevOps
This involves generating a Personal Access Token and configuring the VM as an agent.
π Generate a Personal Access Token (PAT)
The PAT allows secure communication between Azure DevOps and your VM.
Steps:
- Go to your Azure DevOps organization.
- Click User Settings (top-right).
- Select Personal Access Tokens.
- Click New Token.
Configure:
- Provide a name
- Select Full Access (sufficient for learning purposes)
- Click Create
- Copy and securely store the token π
β In production, always follow the Principle of Least Privilege.
π Create an Agent Pool
Next, create a pool where your VM agent will be registered.
- Go to Organization Settings.
- Select Agent Pools.
- Click Add Pool.
- Choose Self-hosted.
- Provide a meaningful name (e.g.,
My Agent Pool). - Allow access to all pipelines if required.
- Click Create.
Your agent pool is now ready β but currently empty.
π» Configure the Linux VM as an Agent
From the Agent Pool page:
- Open your pool.
- Click New Agent.
- Select the Linux tab.
- Follow the provided instructions.
Now switch to your Linux VM.
π Create a Working Directory
mkdir myagent
cd myagent
Verify:
ls
π₯ Download the Agent Package
Run the wget command shown in the Azure DevOps portal:
wget <agent-download-url>
Confirm the file exists:
ls
You should see a .tar.gz file.
π¦ Extract the Package
tar zxvf <agent-file-name>.tar.gz
After extraction, you should see files including:
config.shrun.sh
β Configure the Agent
Run:
./config.sh
Follow the prompts:
- Accept the license (
Y) - Enter your Azure DevOps organization URL
- Choose PAT authentication (press Enter)
- Paste your Personal Access Token
- Enter the agent pool name (
My Agent Pool) - Confirm or provide agent name
If successful, the configuration will complete and return you to the terminal.
βΆ Start the Agent
Run:
./run.sh
If successful, you will see:
Listening for Jobs
Now return to:
Organization Settings β Agent Pools β My Agent Pool
You should see:
- β Your agent listed
- β Status: Online
Your self-hosted Linux VM is now fully connected and ready to execute Azure DevOps pipeline jobs π
π― Section Summary
In this section, we:
- Installed and verified Docker π³
- Discussed installing additional utilities π§°
- Generated a Personal Access Token π
- Created an Agent Pool π
- Configured and started a self-hosted Linux agent π»
Your environment is now prepared to run CI/CD pipelines using your own infrastructure β giving you greater flexibility and control over your DevOps workflows.
π Azure DevOps Pipeline: Build & Push Docker Image to Azure Container Registry
In this section, weβll walk through the complete setup required to build and push a Docker image using Azure DevOps.
This includes:
- π¦ Creating an Azure Container Registry (ACR)
- π§ Creating a pipeline using the Docker template
- π Understanding repository structure
- βοΈ Breaking down the full pipeline YAML code
- π Understanding how Build and Push stages work together
By the end, youβll clearly understand how the pipeline builds your Docker image and pushes it securely to Azure.
Video Explanation
π¦ 1οΈβ£ Create Azure Container Registry (ACR)
Before a pipeline can push an image, we need a registry to store it.
We create this inside:
- Microsoft Azure
π Steps
- Go to the Azure Portal.
- Search for Container Registries.
- Click Create.
- Select:
- Subscription
- Resource Group
- Provide a unique registry name.
- Keep default settings (Basic SKU is fine).
- Click Review + Create β Create.
After deployment:
- Open the registry.
- Click Repositories in the left pane.
At this point, it will be empty.
Our pipeline will push images here.
π§ 2οΈβ£ Create a New Pipeline in Azure DevOps
Now we configure automation in:
- Azure DevOps
π Steps
- Go to Pipelines β Create Pipeline.
- Select Azure Repos Git.
- Choose your repository.
- Select the template:
Docker: Build and Push an image to Azure Container Registry
During setup, Azure DevOps will ask for:
- Azure subscription
- Container registry
- Image repository name
- Dockerfile path
It will also create or use a service connection so DevOps can securely authenticate with Azure.
π 3οΈβ£ Understand the Repository Structure
Before analyzing the pipeline, we need to understand the project structure.
In the repository root, we have three microservices:
- π Result
- π Vote
- π Worker
Each microservice:
- Contains application code
- Has its own Dockerfile
- Requires its own pipeline
In this section, we are working with the Result microservice.
βοΈ Full Pipeline YAML Explained
Below is the pipeline we configured:
trigger:
paths:
include:
- result/*resources:
- repo: selfvariables:
dockerRegistryServiceConnection: 'bc565285-3a3d-4b9b-b39e-52859b47c3ec'
imageRepository: 'resultapp'
containerRegistry: 'myACRForCiCd2.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/result/Dockerfile'
tag: '$(Build.BuildId)'pool:
name: 'myagentpool'stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
steps:
- task: Docker@2
displayName: Build image
inputs:
containerRegistry: '$(dockerRegistryServiceConnection)'
repository: '$(imageRepository)'
command: 'build'
Dockerfile: 'result/Dockerfile'
tags: '$(tag)'- stage: Push
displayName: Push stage
jobs:
- job: Push
displayName: Push
steps:
- task: Docker@2
displayName: Push image
inputs:
containerRegistry: '$(dockerRegistryServiceConnection)'
repository: '$(imageRepository)'
command: 'push'
Dockerfile: 'result/Dockerfile'
tags: '$(tag)'
Letβs break it down.
π Trigger Section
trigger:
paths:
include:
- result/*
This means:
β
The pipeline runs only when changes are made inside the result folder.
It prevents unnecessary builds when other microservices change.
π¦ Resources Section
resources:
- repo: self
self refers to the current repository.
This is the default behavior.
π§ Variables Section
Instead of hardcoding values inside tasks, we defined reusable variables:
| Variable | Purpose |
|---|---|
| dockerRegistryServiceConnection | Authenticates to Azure |
| imageRepository | Name of image in ACR |
| dockerfilePath | Location of Dockerfile |
| tag | Unique image version |
We use:
tag: $(Build.BuildId)
Build.BuildId is a system-generated variable that uniquely identifies the pipeline run.
Each build gets a unique Docker image tag.
π₯ Pool Section
pool:
name: myagentpool
This defines where the pipeline runs.
Both stages use the same self-hosted agent pool.
This is important because Docker images are stored locally on the agent machine.
π Build Stage
Structure:
Stage β Job β Task
The Build stage:
- Contains one job
- That job contains one Docker task
- Command used:
build
What it does:
π Builds the Docker image using result/Dockerfile
π Tags it using $(Build.BuildId)
The image is created locally on the agent machine.
π Push Stage
The Push stage:
- Contains one job
- That job contains one Docker task
- Command used:
push
What it does:
π Pushes the Docker image to Azure Container Registry
π Uses the same image name and tag
π How Build and Push Work Together
There is no automatic transfer of images between stages.
Hereβs what actually happens:
1οΈβ£ Build stage creates the image locally
2οΈβ£ Image is tagged using Build ID
3οΈβ£ Push stage runs on the same agent
4οΈβ£ Push stage pushes that image to ACR
The connection works because:
- Same agent pool
- Same image repository
- Same tag
If the stages ran on different agents, the push would fail because the image would not exist there.
π Complete Workflow Summary
Hereβs the full flow:
1οΈβ£ Developer commits code in result folder
2οΈβ£ Pipeline triggers automatically
3οΈβ£ Build stage creates Docker image
4οΈβ£ Image is tagged uniquely
5οΈβ£ Push stage uploads image to ACR
6οΈβ£ Image appears under ACR β Repositories
π― What This Pipeline Achieves
In this section, we:
- Created Azure Container Registry π¦
- Created a Docker-based Azure DevOps pipeline π§
- Used service connections for secure access π
- Used variables for clean configuration π§
- Built and pushed Docker images automatically π
This setup forms the foundation of a container-based CI pipeline in Azure DevOps.

Leave a Reply