dockerdocker-composeamazon-iamamazon-ecr

Unable to get updated image from ECR using docker compose --pull


I have a private AWS ECR which holds my private docker image. I'm pulling and running this image from within an EC2 in the same account as the private repo. ECR Helper is installed, configured, and working properly as I can pull and run the image

The issue I'm having is related specifically to the docker compose up command using the --pull argument. If I update the image and push to ECR with the same tag, in this case "2.0.0.0-dev", I can see that the image is pushed properly and the hash has changed but when I run docker compose up -d --pull missing --quiet-pull --force-recreate it does not detect the image as missing or updated so it does not pull the latest before recreating the container. If I run docker compose pull, it will detect the image has changed and properly pull the latest

Am I missing something or doing something wrong here? From what I understand the --pull argument with missing should act the same as docker compose pull. Maybe I'm missing a required permission in the IAM policy? Is the "latest" tag treated differently than other tags?

I've tried enabling debug logging for docker but the logs don't tell me much. While testing, I'm running these commands locally on the EC2 instance but this will eventually be used in an ansible script to restart and update the container/image if anything changes, so using --pull always wouldn't be ideal since the ansible script could deploy changes that only require a container restart and not a fresh pull every time. I mainly just want to understand what's going on here because I could work around this by just adding an ansible command to first run docker compose pull then do the restart; it would also be nice for this to work in 1 command vs 2

This is the IAM instance profile used with the EC2

{
    "Statement": [
        {
            "Action": "ecr:GetAuthorizationToken",
            "Effect": "Allow",
            "Resource": "*"
        },
        {
            "Action": [
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "ecr:DescribeImages"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:ecr:us-west-2:XXXXXXXXXXXX:repository/my_app"
        }
    ],
    "Version": "2012-10-17"
}

compose.yaml

---
services:
  my_app:
    image: XXXXXXXXXXXX.dkr.ecr.us-west-2.amazonaws.com/my_app:2.0.0.0-dev
    container_name: my_app
    restart: always
    environment:
      PUID: 1001
      PGID: 1001
      CONNECTION_STRING_FILE: /run/secrets/connection_string
      SLACK_APP_TOKEN_FILE: /run/secrets/slack_app_token
      SLACK_BOT_TOKEN_FILE: /run/secrets/slack_bot_token
    secrets:
      - connection_string
      - slack_app_token
      - slack_bot_token
    volumes:
      - ./config/:/config:ro
      - /var/log/my_app/:/logs/


secrets:
  slack_app_token:
    file: "/etc/my_app/slack_app_token"
  slack_bot_token:
    file: "/etc/my_app/slack_bot_token"
  connection_string:
    file: "/etc/my_app/connection_string"

Solution

  • Looking at what you've shared, let me explain what's happening with your Docker Compose setup. The behavior you're seeing isn't related to IAM permissions or the ECR configuration, but it’s actually about how Docker Compose handles the --pull missing flag.

    When you use --pull missing, Docker Compose only checks if the image tag exists locally. It doesn't verify whether the remote image with the same tag has been updated. That’s why docker compose pull works (it explicitly checks the registry) while --pull missing doesn't catch the update.

    Of course, while you could modify your compose file to use pull_policy: always, this would cause unnecessary pulls every time as you mentioned, which isn't ideal.

    Since you mentioned wanting to use Ansible down the line, here's an approach I suggest, which directly compares the ECR and local image digests:

    - name: Check and update container if needed
      block:
        - name: Get the latest image digest from ECR
          shell: |
            aws ecr describe-images --repository-name my_app --image-ids imageTag=2.0.0.0-dev --query 'imageDetails[0].imageDigest' --output text
          register: ecr_digest
    
        - name: Get the local image digest
          shell: |
            docker inspect --format='{{index .RepoDigests 0}}' XXXXXXXXXXXX.dkr.ecr.us-west-2.amazonaws.com/my_app:2.0.0.0-dev | cut -d "@" -f2
          register: local_digest
          ignore_errors: yes  # In case the image isn't there yet
    
        - name: Pull and restart if image has changed
          when: ecr_digest.stdout != local_digest.stdout or local_digest.rc is defined
          shell: docker compose up -d --pull always --force-recreate
    
        - name: Log update status
          debug:
            msg: "{{ 'Container updated with new image' if (ecr_digest.stdout != local_digest.stdout or local_digest.rc is defined) else 'Already running latest version' }}"
    

    But in the interim, while you're testing locally on the EC2, you'll need to run the two commands separately as you suggested: first docker compose pull to check for updates, then docker compose up to restart the container.