Docker + Azure DevOps Test Environments
How to use Docker containers with Azure DevOps pipelines to create consistent, reproducible test environments. Covers Docker Compose for local dev.
Docker eliminates the "works on my machine" problem in test automation. When your test environment is defined in code (a Dockerfile or docker-compose.yml), the same environment runs locally, in CI, and in every future pipeline run.
Service containers in Azure Pipelines
Azure Pipelines natively supports Docker service containers — containers that run alongside your pipeline job. This is the simplest way to add a database, cache, or mock server to your test pipeline.
YAML1trigger: 2 branches: 3 include: [main] 4 5pool: 6 vmImage: ubuntu-latest 7 8# Service containers run alongside the job 9resources: 10 containers: 11 - container: postgres 12 image: postgres:16 13 env: 14 POSTGRES_PASSWORD: testpass 15 POSTGRES_DB: testdb 16 POSTGRES_USER: testuser 17 ports: 18 - 5432:5432 19 options: >- 20 --health-cmd "pg_isready -U testuser" 21 --health-interval 10s 22 --health-timeout 5s 23 --health-retries 5 24 25 - container: redis 26 image: redis:7-alpine 27 ports: 28 - 6379:6379 29 30jobs: 31 - job: IntegrationTests 32 services: 33 postgres: postgres 34 redis: redis 35 36 steps: 37 - task: NodeTool@0 38 inputs: 39 versionSpec: '20.x' 40 41 - script: npm ci 42 43 - script: npm run db:migrate 44 displayName: Run database migrations 45 env: 46 DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb 47 48 - script: npm run test:integration 49 displayName: Run integration tests 50 env: 51 DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb 52 REDIS_URL: redis://localhost:6379 53 54 - task: PublishTestResults@2 55 inputs: 56 testResultsFormat: JUnit 57 testResultsFiles: test-results/integration.xml 58 condition: always()
Running tests in a Docker container
For consistent browser and dependency versions:
YAML1pool: 2 vmImage: ubuntu-latest 3 4steps: 5 - script: | 6 docker run --rm \ 7 -v $(Build.SourcesDirectory):/workspace \ 8 -w /workspace \ 9 -e BASE_URL=$(STAGING_URL) \ 10 -e CI=true \ 11 mcr.microsoft.com/playwright:v1.45.0-jammy \ 12 npx playwright test 13 displayName: Run Playwright in container 14 15 - task: PublishTestResults@2 16 inputs: 17 testResultsFormat: JUnit 18 testResultsFiles: test-results/results.xml 19 condition: always()
This guarantees identical Playwright and browser versions between local runs and CI.
Docker Compose for local development matching CI
YAML1# docker-compose.test.yml 2version: '3.8' 3services: 4 postgres: 5 image: postgres:16 6 environment: 7 POSTGRES_PASSWORD: testpass 8 POSTGRES_DB: testdb 9 POSTGRES_USER: testuser 10 ports: 11 - '5432:5432' 12 healthcheck: 13 test: ["CMD-SHELL", "pg_isready -U testuser"] 14 interval: 10s 15 timeout: 5s 16 retries: 5 17 18 redis: 19 image: redis:7-alpine 20 ports: 21 - '6379:6379' 22 23 app: 24 build: . 25 environment: 26 DATABASE_URL: postgresql://testuser:testpass@postgres:5432/testdb 27 REDIS_URL: redis://redis:6379 28 depends_on: 29 postgres: 30 condition: service_healthy 31 ports: 32 - '3000:3000'
Run locally:
BASH1docker-compose -f docker-compose.test.yml up -d 2npm run test:integration 3docker-compose -f docker-compose.test.yml down
The same database, same Redis, same app version as CI.
Building and caching Docker images in pipelines
YAML1variables: 2 DOCKER_BUILDKIT: '1' 3 IMAGE_NAME: $(Build.Repository.Name) 4 IMAGE_TAG: $(Build.BuildId) 5 6steps: 7 - task: Docker@2 8 displayName: Build test image 9 inputs: 10 command: build 11 dockerfile: Dockerfile.test 12 tags: $(IMAGE_NAME):$(IMAGE_TAG) 13 arguments: --build-arg BUILDKIT_INLINE_CACHE=1 14 15 - task: Docker@2 16 displayName: Push to ACR 17 inputs: 18 command: push 19 containerRegistry: $(ACR_SERVICE_CONNECTION) 20 repository: $(IMAGE_NAME) 21 tags: $(IMAGE_TAG)
Common errors and fixes
Error: Service container shows as unhealthy in pipeline
Fix: Add a health check and --health-retries option. The pipeline starts the job before the container is ready without health checks. Use options: --health-cmd "..." --health-retries 5.
Error: cannot connect to localhost:5432 in the test job
Fix: Service containers are accessed via localhost in Azure Pipelines. Ensure the port mapping is correct (5432:5432) and the connection string uses localhost not the container name.
Error: Docker build fails with "permission denied" on Linux agents
Fix: Add the current user to the docker group: - script: sudo usermod -aG docker $USER && newgrp docker. Or use sudo docker build in the script.
Error: Tests pass in Docker locally but fail in pipeline container
Fix: Check environment variable mapping. Variables passed with -e flag in local Docker run must be set as pipeline variables. Also check file mount paths — $(Build.SourcesDirectory) is the correct pipeline path.
Share this article
Follow for more
Follow me on social media for more developer tips, tricks, and tutorials. Let's connect and build something great together!