I have yet to try IBM Cloud and decided with Docker’s latest rate limiting on free accounts, it might be worth looking into alternate stores for containers.  IBM Cloud has a small number of free tier options one of which is a small Container Registry.

One thing you must understand before we move forward is the difference between something like Harbor.io and ACR and the rest; ECR, GCR, IBM CR and Docker Hub.  The former are infrastructure plays - that is, the container registry is yours and yours alone with a unique name.  The latter is a “namespace” on a shared cluster.  

This often lowers cost but one needs to be aware that you are using shared infrastructure - that a “namespace” is just an isolated prefix (like a Github path) and not an instance (like an isolated website).

Create an Account

First we need to create an IBM Cloud account if we haven’t already.

Click next on through to create

Once created, login to IBM Cloud

Create a CR

Let’s create a CR in the Free tier..

The free tier includes 500mb of storage and 5gb of pull. But the calculator still shows costs.

Using the Registry

We can follow the guide here.

In WSL, we will install the IBM Cloud CLI and the CR Plugin.  All things in IBM Cloud CLI are added in as plugins.

builder@DESKTOP-2SQ9NQM:~$ curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
Current platform is linux64. Downloading corresponding IBM Cloud CLI...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   111  100   111    0     0    466      0 --:--:-- --:--:-- --:--:--   466
100 13.9M  100 13.9M    0     0  5882k      0  0:00:02  0:00:02 --:--:-- 8558k
Download complete. Executing installer...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    40  100    40    0     0    223      0 --:--:-- --:--:-- --:--:--   224
[sudo] password for builder:
Install complete.

Install the container registry plugin:

$ ibmcloud plugin install container-registry -r 'IBM Cloud'
Looking up 'container-registry' from repository 'IBM Cloud'...
Plug-in 'container-registry 0.1.497' found in repository 'IBM Cloud'
Attempting to download the binary file...
 28.56 MiB / 28.56 MiB [====================================================================] 100.00% 1s
29945856 bytes downloaded
Installing binary...
Plug-in 'container-registry 0.1.497' was successfully installed into /home/builder/.bluemix/plugins/container-registry. Use 'ibmcloud plugin show container-registry' to show its details.

Now login into IBM Cloud

$ ibmcloud login -a https://cloud.ibm.com
API endpoint: https://cloud.ibm.com

Email> isaac.johnson@gmail.com


Targeted account Isaac Johnson's Account (218912341234123412341234123478a0b)

Select a region (or press enter to skip):
1. au-syd
2. in-che
3. jp-osa
4. jp-tok
5. kr-seo
6. eu-de
7. eu-gb
8. ca-tor
9. us-south
10. us-east
Enter a number> 9
Targeted region us-south

API endpoint:      https://cloud.ibm.com
Region:            us-south
User:              isaac.johnson@gmail.com
Account:           Isaac Johnson's Account (218912341234123412341234123478a0b)
Resource group:    No resource group targeted, use 'ibmcloud target -g RESOURCE_GROUP'
CF API endpoint:

We'd like to collect usage statistics to help improve the IBM Cloud CLI.
This data will never be shared outside IBM.
To learn more, see the IBM Privacy Policy: https://www.ibm.com/privacy
You can enable or disable usage data collection by running 'ibmcloud config --usage-stats-collect [true | false]'

Do you want to send usage statistics to IBM? [y/n]> n

Verify we have us-south set (we should).

$ ibmcloud cr region-set us-south
The region is set to 'us-south', the registry is 'us.icr.io'.


So now we add a namespace in the registry (as opposed to a brand new registry)

$ ibmcloud cr namespace-add freshbrewedcr
No resource group is targeted. Therefore, the default resource group for the account ('Default') is targeted.

Adding namespace 'freshbrewedcr' in resource group 'Default' for account Isaac Johnson's Account in registry us.icr.io...

Successfully added namespace 'freshbrewedcr'


We now need to create an API KEY to use later for login.

$ ibmcloud iam api-key-create MyKey -d "this is my API key" --file ibmAPIKeyFile
Creating API key MyKey under 218912341234123412341238a0b as isaac.johnson@gmail.com...
API key MyKey was created
Successfully save API key information to ibmAPIKeyFile
builder@DESKTOP-2SQ9NQM:~$ cat ibmAPIKeyFile
        "id": "ApiKey-27a882c5-fe12-4dbf-8a03-17fe3eea8171",
        "crn": "crn:v1:bluemix:public:iam-identity::a/2189123123123123123123123bF::apikey:ApiKey-27a81233-1231-1233-1233-12333eea8171",
        "iam_id": "IBMid-550009Q6G9",
        "account_id": "218912312312312312312378a0b",
        "name": "MyKey",
        "description": "this is my API key",
        "apikey": "K2xL71231231231231231212321312gl-r",
        "locked": false,
        "entity_tag": "1-4512312312312312312312312df",
        "created_at": "2020-11-20T01:28+0000",
        "created_by": "IBMid-550009Q6G9",
        "modified_at": "2020-11-20T01:28+0000"

Then we create a new principal, an IBM Service ID

builder@DESKTOP-2SQ9NQM:~$ ibmcloud iam service-id-create localk3s-default-id --description "Service ID for IBM Cloud Container Registry in Kubernetes Cluster k3s namesace default"
Creating service ID localk3s-default-id bound to current account as isaac.johnson@gmail.com...
Service ID localk3s-default-id is created successfully

ID            ServiceId-asdfasdf-123123-123123-1232131-123123123bab8a
Name          localk3s-default-id
Description   Service ID for IBM Cloud Container Registry in Kubernetes Cluster k3s namespace default
CRN           crn:v1:bluemix:public:iam-identity::a/218123121231231238a0b::serviceid:ServiceId-123123123-123123-123123-123123-23123123
Version       1-1039123123121231320548
Locked        false

Bind that ID to a Manager role via a policy

builder@DESKTOP-2SQ9NQM:~$ ibmcloud iam service-policy-create localk3s-default-id --roles Manager
Creating policy under current account for service ID localk3s-default-id as isaac.johnson@gmail.com...
Service policy is successfully created

Policy ID:   fa1e344f-1234-1234-1234-e625d96a4eac
Version:     1-818312312312312312312bc4ed79
Roles:       Manager
             Service Type   All resources in account

An lastly, for that IBM Service ID we created, we want an API Key to go with it

builder@DESKTOP-2SQ9NQM:~$ ibmcloud iam service-api-key-create localk3s-default-key localk3s-default-id --description 'API key for service ID localk3s-default-id is Kubernetes cluster localk3s namespace default'
Creating API key localk3s-default-key of service ID localk3s-default-id under account 2189dasdfasdfasdfsadfsadf8a0b as isaac.johnson@gmail.com...
Service ID API key localk3s-default-key is created

Please preserve the API key! It cannot be retrieved after it's created.

ID            ApiKey-asdfsadfsad-asdfd-asdf-asdf-e0eeb9333714
Name          localk3s-default-key
Description   API key for service ID localk3s-default-id is Kubernetes cluster localk3s namespace default
Created At    2020-11-20T02:32+0000
API Key       cyM2asdfasdfsadfasdfsafasdfog8C
Locked        false

Using the CR

Login to your K8s cluster (using K3s here)

builder@DESKTOP-2SQ9NQM:~$ kubectl get nodes
NAME               STATUS   ROLES    AGE    VERSION
isaac-macbookair   Ready    <none>   10h    v1.19.3+k3s3
isaac-macbookpro   Ready    master   4d3h   v1.19.3+k3s3

Create a CR Secret.. We will be referencing the API Key we created above (cyM2asdfasdfsadfasdfsafasdfog8C).  The user will be "iamapikey" and the email should match your IBM login.

builder@DESKTOP-2SQ9NQM:~$ kubectl create secret docker-registry ibmregcred  --docker-server=us.icr.io --docker-username=iamapikey --docker-password=cyM2asdfasdfsadfasdfsafasdfog8C --docker-email=isaac.johnson@gmail.com
secret/ibmregcred created

We can also login to the IBM CR locally:

builder@DESKTOP-2SQ9NQM:~$ ibmcloud cr login
Logging in to 'registry.ng.bluemix.net'...
Logged in to 'registry.ng.bluemix.net'.
Logging in to 'us.icr.io'...
Logged in to 'us.icr.io'.


Pushing a Hello World Example

$ docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
Digest: sha256:e7c70bb24b462baa86c102610182e3efcb12a04854e8c582838d92970a09f323
Status: Image is up to date for hello-world:latest

then we can tag and push.

builder@DESKTOP-JBA79RT:~$ docker images | grep hello
hello-world                                  latest                  bf756fb1ae65        10 months ago       13.3kB
builder@DESKTOP-JBA79RT:~$ docker tag hello-world:latest us.icr.io/freshbrewedcr/hw_repo:1
builder@DESKTOP-JBA79RT:~$ docker push us.icr.io/freshbrewedcr/hw_repo:1
The push refers to repository [us.icr.io/freshbrewedcr/hw_repo]
9c27e219663c: Layer already exists
1: digest: sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 size: 525

Create the k8s deployment YAML that will use this and apply

builder@DESKTOP-2SQ9NQM:~$ cat ibm_image.yaml
apiVersion: apps/v1
kind: Deployment
  name: helloworld-deployment
    app: helloworld
  replicas: 1
      app: helloworld
        app: helloworld
      - name: helloworld
        image: us.icr.io/freshbrewedcr/hw_repo:1
        - containerPort: 80
      - name: ibmregcred

builder@DESKTOP-2SQ9NQM:~$ kubectl apply -f ibm_image.yaml
deployment.apps/helloworld-deployment created

Verify it ran (hello-world doesn't expose a service so it will crash)

builder@DESKTOP-2SQ9NQM:~$ kubectl describe pod helloworld-deployment-7f5cff7cf8-7qgms | tail -n10
  Type     Reason     Age                From                       Message
  ----     ------     ----               ----                       -------
  Normal   Scheduled  <unknown>                                     Successfully assigned default/helloworld-deployment-7f5cff7cf8-7qgms to isaac-macbookair
  Normal   Pulling    73s                kubelet, isaac-macbookair  Pulling image "us.icr.io/freshbrewedcr/hw_repo:1"
  Normal   Pulled     68s                kubelet, isaac-macbookair  Successfully pulled image "us.icr.io/freshbrewedcr/hw_repo:1" in 5.496330884s
  Normal   Pulled     20s (x3 over 67s)  kubelet, isaac-macbookair  Container image "us.icr.io/freshbrewedcr/hw_repo:1" already present on machine
  Normal   Created    20s (x4 over 68s)  kubelet, isaac-macbookair  Created container helloworld
  Normal   Started    20s (x4 over 67s)  kubelet, isaac-macbookair  Started container helloworld
  Warning  BackOff    20s (x5 over 66s)  kubelet, isaac-macbookair  Back-off restarting failed container

builder@DESKTOP-2SQ9NQM:~$ kubectl logs helloworld-deployment-7f5cff7cf8-7qgms

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:

For more examples and ideas, visit:

We can also see the entry in IBM Cloud's Registry

While namespaces show our images collapsed under our namespaces, Repositories lets use look at our specific image repos

We can look at specific images in the images tab

You can delete from images to save space

And from there get image details

Security Checks

A nice feature for this free offering I haven't seen elsewhere (for free) is built in security scanning.  Here I pushed an image that was flagged:

If we click on the issue we can see details

Be aware that this was found while writing this blog entry (2020-11-18 is the date for this CVE).  So it proves it is active scanning existing images.

Our settings show us how much of our quota we have used.

Azure DevOps Integration

Let's add our CR to Azure DevOps to be used in our CICD. First create new Docker Registry Service Connection in a project.

Set the user and password as the key

note the service connection ID in the URL.. we use that in the YAML pipelines below

Then update the pipelines to use it:

  # Container registry service connection established during pipeline creation
  dockerRegistryServiceConnection: '7db7c6a9-f749-4289-a33e-f7204dd7bfe2'
  imageRepository: 'freshbrewedcr/hello'
  containerRegistry: 'us.icr.io'
  dockerfilePath: '**/Dockerfile'
  tag: '$(Build.BuildId)'
  imagePullSecret: 'ijk8senv5cr72877afc-auth'

Note: we need to use our “repository” in the imageRepository path (so set it as $REPO/$IMAGE)

Run a build and verify it pushed:

And we can see it in IBM Cloud


IBM CR is a small but nice offering.  At 500Mb it really lends itself to small images.  However, there is nothing wrong with a small free offering. I never look a gift horse in the mouth.  I will be looking into Harbor.io next and perhaps using something like IBM Cloud's CR would pair nice for permanent storage of "released" images.

An aside: In a large section I didn't include, but spent several days on, I attempted to setup k3s on a Google Cloud F1 (free tier) instance with the hopes of showing a great free+free option (following this guide).  No matter how I tried (and I tried everything), the tiny f1-micro class (1ecu, 640mb ram) just couldn't hold up to run k3s master node.  So I scratched the whole bit.  However, I will continue to search for a good free'ish container host one could pair with this CR.