Volver a las notas

Jenkins no puede hacer push a Amazon ECR: permisos IAM y login

17 de julio de 2026·
dockerawsecrjenkins

Nota revisada y reescrita el 18 de octubre de 2026: la versión original tenía errores y datos sin fuente.

La build pasa, la imagen se construye y el docker push a Amazon ECR falla con no basic auth credentials, denied: Your authorization token has expired o un AccessDeniedException. Casi siempre es una de dos cosas: el rol de IAM no tiene los permisos bien escritos, o Docker nunca se autenticó contra el registro.

1. La política IAM

Para subir una imagen hacen falta estas acciones:

  • ecr:GetAuthorizationToken
  • ecr:BatchCheckLayerAvailability
  • ecr:InitiateLayerUpload
  • ecr:UploadLayerPart
  • ecr:CompleteLayerUpload
  • ecr:PutImage

La trampa está en la primera: GetAuthorizationToken no se puede limitar a un repositorio. Pide un token para el registro entero, así que su Resource tiene que ser "*". Si la metes en el mismo bloque que las demás con el ARN del repositorio, la llamada falla aunque el resto esté bien. Lo correcto son dos bloques:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ecr:GetAuthorizationToken",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload",
        "ecr:PutImage"
      ],
      "Resource": "arn:aws:ecr:REGION:ACCOUNT_ID:repository/NOMBRE_REPO"
    }
  ]
}

Si Jenkins corre en una instancia EC2, asigna esta política a un rol y el rol a la instancia (instance profile). Así no hay claves de acceso guardadas en Jenkins.

2. El login de Docker

Tener el rol en la instancia no autentica a Docker. Docker solo sabe hablar con el registro si alguien le da un usuario y una contraseña, y en ECR la contraseña es un token temporal que caduca a las 12 horas. Hay que pedirlo en cada build, antes del push:

pipeline {
  agent any
  environment {
    REGION   = 'eu-west-1'
    REGISTRY = '123456789012.dkr.ecr.eu-west-1.amazonaws.com'
    IMAGE    = "${REGISTRY}/mi-app:${env.BUILD_NUMBER}"
  }
  stages {
    stage('Build') {
      steps { sh 'docker build -t $IMAGE .' }
    }
    stage('Push') {
      steps {
        sh '''
          aws ecr get-login-password --region $REGION \
            | docker login --username AWS --password-stdin $REGISTRY
          docker push $IMAGE
        '''
      }
    }
  }
}

El usuario es literalmente AWS. --password-stdin evita que el token aparezca en la lista de procesos o en el log de Jenkins.

Si prefieres no hacer login a mano, el Amazon ECR credential helper (docker-credential-ecr-login) obtiene el token solo, usando el rol de la instancia. Se configura en ~/.docker/config.json del usuario que ejecuta Docker en el agente de Jenkins:

{ "credHelpers": { "123456789012.dkr.ecr.eu-west-1.amazonaws.com": "ecr-login" } }

Cómo saber cuál de las dos es

  • aws sts get-caller-identity en un paso del pipeline te dice qué identidad está usando de verdad: puede no ser el rol que crees.
  • aws ecr get-login-password falla con AccessDeniedException: el problema es la política. Mira el Resource de GetAuthorizationToken.
  • El login funciona pero el push devuelve denied: faltan acciones de subida de capas, o el ARN del repositorio no coincide (región, cuenta o nombre).
  • no basic auth credentials: Docker no hizo login, o lo hizo otro usuario del sistema distinto al que ejecuta el push.
  • name unknown: The repository ... does not exist: el repositorio no existe. Créalo antes con aws ecr create-repository, Terraform o la consola. Otra opción: desde diciembre de 2025, ECR puede crearlo al hacer push (create on push), pero solo si hay una repository creation template cuyo prefijo coincida con el nombre. Sin plantilla, el push falla igual.

Fuentes