Microsoft-hosted agents are convenient but sometimes you need self-hosted: private network access, specific software, or just more control. Running agents in containers gives you the best of both worlds.
Why Container Agents?
Compared to VM-based agents:
- Faster provisioning - Seconds vs minutes
- Better resource utilisation - Multiple agents per host
- Consistent environment - Same image everywhere
- Easy scaling - Spin up more containers as needed
- Simpler updates - Rebuild the image, restart containers
Base Agent Dockerfile
FROM ubuntu:22.04
# Avoid prompts during package installation
ENV DEBIAN_FRONTEND=noninteractive
# Install dependencies
RUN apt-get update && apt-get install -y \
curl \
git \
jq \
libicu70 \
libssl3 \
ca-certificates \
apt-transport-https \
software-properties-common \
&& rm -rf /var/lib/apt/lists/*
# Install Azure CLI
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash
# Install PowerShell
RUN curl -sSL https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \
&& echo "deb https://packages.microsoft.com/repos/microsoft-ubuntu-jammy-prod jammy main" > /etc/apt/sources.list.d/microsoft.list \
&& apt-get update \
&& apt-get install -y powershell \
&& rm -rf /var/lib/apt/lists/*
# Install Terraform
RUN curl -sSL https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip -o terraform.zip \
&& unzip terraform.zip \
&& mv terraform /usr/local/bin/ \
&& rm terraform.zip
# Create agent directory
WORKDIR /azp
RUN mkdir -p /azp/agent
# Download and extract agent
ARG AGENT_VERSION=3.227.2
RUN curl -sSL https://vstsagentpackage.azureedge.net/agent/${AGENT_VERSION}/vsts-agent-linux-x64-${AGENT_VERSION}.tar.gz | tar -xz -C /azp/agent
# Start script
COPY start.sh /azp/
RUN chmod +x /azp/start.sh
ENTRYPOINT ["/azp/start.sh"]
Start Script
#!/bin/bash
set -e
if [ -z "$AZP_URL" ]; then
echo "error: missing AZP_URL environment variable"
exit 1
fi
if [ -z "$AZP_TOKEN" ]; then
echo "error: missing AZP_TOKEN environment variable"
exit 1
fi
if [ -z "$AZP_POOL" ]; then
AZP_POOL="Default"
fi
cd /azp/agent
# Configure the agent
./config.sh --unattended \
--url "$AZP_URL" \
--auth pat \
--token "$AZP_TOKEN" \
--pool "$AZP_POOL" \
--agent "${AZP_AGENT_NAME:-$(hostname)}" \
--replace \
--acceptTeeEula
# Run the agent
./run.sh
Docker-in-Docker
If your pipelines need to build Docker images, you have two options:
Option 1: Docker Socket Mounting
Mount the host's Docker socket:
docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
-e AZP_URL="https://dev.azure.com/yourorg" \
-e AZP_TOKEN="your-pat" \
-e AZP_POOL="ContainerAgents" \
your-agent-image
Pros: Simpler, uses host's Docker daemon Cons: Security risk - container can access all host containers
Option 2: Docker-in-Docker (DinD)
Run Docker daemon inside the container:
# Add to Dockerfile
RUN curl -fsSL https://get.docker.com | sh
# Start script needs to start dockerd
dockerd &
sleep 5 # Wait for daemon to start
Run with privileged mode:
docker run -d --privileged \
-e AZP_URL="https://dev.azure.com/yourorg" \
-e AZP_TOKEN="your-pat" \
your-agent-image
Pros: Isolated Docker daemon Cons: Requires privileged mode, more complex
Kubernetes Deployment
For Kubernetes, use a deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: azdo-agent
spec:
replicas: 3
selector:
matchLabels:
app: azdo-agent
template:
metadata:
labels:
app: azdo-agent
spec:
containers:
- name: agent
image: your-registry/azdo-agent:latest
env:
- name: AZP_URL
value: "https://dev.azure.com/yourorg"
- name: AZP_TOKEN
valueFrom:
secretKeyRef:
name: azdo-pat
key: token
- name: AZP_POOL
value: "K8sAgents"
Security Considerations
- PAT tokens - Use minimal scope (Agent Pools read/manage)
- Socket mounting - Only if you trust all pipeline code
- Privileged mode - Avoid if possible
- Image scanning - Scan your agent images for vulnerabilities
- Network policies - Restrict agent egress in Kubernetes
Need help setting up CI/CD infrastructure? Get in touch - we help teams build efficient DevOps pipelines.