Docker Image Promotion in Artifactory with Azure DevOps YAML Pipelines

You can promote an image from one docker image repository to another by re-tagging and pushing your image.

-

As part of any good pipeline to production, the separation of environments is always a bit tricky to get right. Organizationally, it might make sense to give your developers freedom to iterate their pipelines and application code in development environments, without letting them go directly to QA or production environments. With Azure DevOps a high level of control can be maintained around service connections, environments (checks), agent pools, and even application development/branching merging policies can be enforced. With YAML pipelines, limiting QA and production service connections to Azure DevOps Project Administrators is a great way to review and authorize developer pipelines before they go doing things in further environments.

Once you've created a service connection to each dev, qa, and production Artifactory Docker Image Repository, and limited the QA and production service connections to Project Administrators, you can start coding up an image promotion strategy in your YAML pipeline. Conceptually, you can promote an image from one docker image repository to another by re-tagging and pushing your image:

docker build -t my-test-image .
docker tag my-test-image my-url.com/docker-local-dev/rminchuk-poc:0da098bf
docker login "my-url.com"
docker push my-url.com/docker-local-dev/rminchuk-poc:0da098bf
# ... deploy/test your image in dev
docker pull my-url.com/docker-local-dev/rminchuk-poc:0da098bf
docker tag my-url.com/docker-local-dev/rminchuk-poc:0da098bf \
           my-url.com/docker-local-qa/rminchuk-poc:0da098bf
docker push my-url.com/docker-local-qa/rminchuk-poc:0da098bf

Below follows a brief example of how you can automate the promotion of a docker image from one environment to another conceptual environment, housed inside varying environmentally tiered Docker Image Repositories in Artifactory.

stages:
# build and push docker image
# ....
# 'Deploy to Dev'
# ...
- stage: Promote_image_to_QA
  dependsOn: 'Deploy_to_Dev1'
  jobs:
  - job:
    displayName: Promote_image_to_QA
    pool: my-agent-pool # or a hosted agent pool
    steps:
    - task: ArtifactoryDocker@1
      inputs:
        command: 'pull'
        artifactoryService: 'artifactory-dev-publisher'
        sourceRepo: 'docker-local-dev'
        imageName: "my-url.com/docker-local-dev/rminchuk-poc:$(Build.SourceVersion)-$(Build.BuildId)"
    - bash: |
        docker tag "my-url.com/docker-local-dev/rminchuk-poc:$(Build.SourceVersion)-$(Build.BuildId)" \
                   "my-url.com/docker-local-qa/rminchuk-poc:$(Build.SourceVersion)-$(Build.BuildId)"
    - task: ArtifactoryDocker@1
      inputs:
        command: 'push'
        artifactoryService: 'artifactory-qa-publisher'
        targetRepo: 'docker-local-qa'
        imageName: "my-url.com/docker-local-qa/rminchuk-poc:$(Build.SourceVersion)-$(Build.BuildId)"
# ...

Further, this strategy can be used with docker hub as well (depending on whether or not you want to pay for multiple private Docker Image Repositories in docker hub).

stages:
- stage: Build
  jobs:
  - job:
    pool: my-agent-pool
    steps:
    - task: Docker@2
      displayName: 'Login'
      inputs:
        command: login
        containerRegistry: dockerhub-dev
    - task: Docker@2
      displayName: Build and Push
      inputs:
        command: buildAndPush
        repository: docker-local-dev/rminchuk-poc
        tags: |
          latest
          $(Build.SourceBranchName)
          $(Build.SourceVersion)-$(Build.BuildId)
    - task: Docker@2
      displayName: 'Logout'
      inputs:
        command: logout
        containerRegistry: dockerhub-dev
# ...
# 'Deploy to Dev'
# ...
- stage: Promote_image_to_QA
  dependsOn: 'Deploy_to_Dev'
  jobs:
  - job:
    displayName: Promote_image_to_QA
    pool: my-agent-pool # or a hosted agent pool
    steps:
    - task: Docker@2
      displayName: 'Login'
      inputs:
        command: login
        containerRegistry: dockerhub-dev
    - bash: |
        docker pull "docker-local-dev/rminchuk-poc:$(Build.SourceVersion)-$(Build.BuildId)"
    - task: Docker@2
      displayName: 'Logout'
      inputs:
        command: logout
        containerRegistry: dockerhub-dev
    - task: Docker@2
      displayName: 'Login'
      inputs:
        command: login
        containerRegistry: dockerhub-qa
    - bash: |
        docker tag "docker-local-dev/rminchuk-poc:$(Build.SourceVersion)-$(Build.BuildId)" \
                   "docker-local-qa/rminchuk-poc:$(Build.SourceVersion)-$(Build.BuildId)"
        docker push "docker-local-qa/rminchuk-poc:$(Build.SourceVersion)-$(Build.BuildId)"
    - task: Docker@2
      displayName: 'Logout'
      inputs:
        command: logout
        containerRegistry: dockerhub-qa

If you liked this article, let me know by upvoting my answer on stack exchange. Thanks for reading!

Rich Minchuk

Technology Enthusiast and Wannabe Growth Hacker