Search code examples
pythondockerdockerpy

Docker CLI allows tag, but Docker Python API raises APIError


I am trying to push a local Docker image to ECR using the Docker Python API. As part of that process I need to tag the image a certain way. When I do so on the CLI, it works:

docker tag foo/bar '{user_id}.dkr.ecr.us-east-1.amazonaws.com/foo/bar'

However when I try to do the same thing using the docker.images.Image.tag function in the Docker Python SDK it fails:

import docker
(docker.client.from_env().images.get('foo/bar')
 .tag('foo/bar', 
      '{user-id}.dkr.ecr.us-east-1.amazonaws.com/foo/bar'
     )
)

(replace user_id in the code samples above with an AWS user id value, e.g. 717171717171; I've obfuscated it here for the purposes of this question)

With the following error:

In [10]: docker.client.from_env().images.get('foo/bar').ta
    ...: g('foo/bar', '{user_id}.dkr.ecr.us-east-1.amaz
    ...: onaws.com/foo/bar')                              
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
~/miniconda3/envs/alekseylearn-dev/lib/python3.6/site-packages/docker/api/client.py in _raise_for_status(self, response)
    255         try:
--> 256             response.raise_for_status()
    257         except requests.exceptions.HTTPError as e:

~/miniconda3/envs/alekseylearn-dev/lib/python3.6/site-packages/requests/models.py in raise_for_status(self)
    939         if http_error_msg:
--> 940             raise HTTPError(http_error_msg, response=self)
    941 

HTTPError: 500 Server Error: Internal Server Error for url: http+docker://localhost/v1.35/images/sha256:afe07035bce72b6c496878a7e3960bedffd46c1bedc79f1bd2b89619e8457194/tag?tag={user_id}.dkr.ecr.us-east-1.amazonaws.com%2Ffoo%2Fbar&repo=foo%2Fbar&force=0

During handling of the above exception, another exception occurred:

APIError                                  Traceback (most recent call last)
<ipython-input-10-5bb015d17409> in <module>
----> 1 docker.client.from_env().images.get('alekseylearn-example/build').tag('foo/bar', '{user_id}.dkr.ecr.us-east-1.amazonaws.com/foo/bar')

~/miniconda3/envs/alekseylearn-dev/lib/python3.6/site-packages/docker/models/images.py in tag(self, repository, tag, **kwargs)
    120             (bool): ``True`` if successful
    121         """
--> 122         return self.client.api.tag(self.id, repository, tag=tag, **kwargs)
    123 
    124 

~/miniconda3/envs/alekseylearn-dev/lib/python3.6/site-packages/docker/utils/decorators.py in wrapped(self, resource_id, *args, **kwargs)
     17                     'Resource ID was not provided'
     18                 )
---> 19             return f(self, resource_id, *args, **kwargs)
     20         return wrapped
     21     return decorator

~/miniconda3/envs/alekseylearn-dev/lib/python3.6/site-packages/docker/api/image.py in tag(self, image, repository, tag, force)
    531         url = self._url("/images/{0}/tag", image)
    532         res = self._post(url, params=params)
--> 533         self._raise_for_status(res)
    534         return res.status_code == 201
    535 

~/miniconda3/envs/alekseylearn-dev/lib/python3.6/site-packages/docker/api/client.py in _raise_for_status(self, response)
    256             response.raise_for_status()
    257         except requests.exceptions.HTTPError as e:
--> 258             raise create_api_error_from_http_exception(e)
    259 
    260     def _result(self, response, json=False, binary=False):

~/miniconda3/envs/alekseylearn-dev/lib/python3.6/site-packages/docker/errors.py in create_api_error_from_http_exception(e)
     29         else:
     30             cls = NotFound
---> 31     raise cls(e, response=response, explanation=explanation)
     32 
     33 

APIError: 500 Server Error: Internal Server Error ("invalid tag format")

Why does the CLI command succeed and the Python API command fail?


Solution

  • In detailed Docker API lingo, an image name like 123456789012.dkr.ecr.us-east-1.amazon.aws.com/foo/bar:baz is split up into a repository (before the colon) and a tag (after the colon). The host-name part of the repository name is a registry. The default tag value if none is specified is the literal latest.

    In your case, you already have an Image object, so you need to apply the two "halves" of the second argument:

    docker.client.from_env().images.get('foo/bar')
     .tag('{user-id}.dkr.ecr.us-east-1.amazonaws.com/foo/bar',
          'latest'
         )
    

    (In many practical cases using the latest tag isn't a great idea; something like a timestamp or source control commit ID better identifies the image and helps indicate to services like ECS or EKS or plain Kubernetes that they need to do an update. Also, while the ECR image IDs are kind of impractically long, in a scripting context nothing stops you from using them directly; you can, for example, docker build -t 12345...amazonaws.com/foo/bar:abcdef0 and skip the intermediate docker tag step if you want.)