DevOps 3 min read

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.

I
InnovateBits
InnovateBits

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.

YAML
1trigger: 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:

YAML
1pool: 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

YAML
1# 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:

BASH
1docker-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

YAML
1variables: 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.

Tags
#docker#azure-devops#test-environments#docker-compose#service-containers#ci-cd#test-automation

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!