Creating a local container registry

I feel love I’ve written a post on this before, but it doesn’t hurt to keep things upto date.

I set up a Kubernetes instance along with container registry etc. within Azure, but if all you want to do is run things locally and at zero cost (other than you usual cost of running a computer), you might want to set up a local container registry.

I’m doing this on Windows for this post, but I expect it’s pretty much the same on Linux and Mac, but in my case I am also running Docker Desktop.

docker-compose

We’re going to stand up the registry using docker-compose, but if you’d like to just run the registry from the simple docker run command, you can use this

docker run -d -p 5000:5000 --name registry registry:2

However you’ll probably want a volume set-up, along with a web UI, so let’s put all that together into the docker-compose.yml file below

version: '3.8'

services:
  registry:
    image: registry:2
    container_name: container-registry
    ports:
      - "5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: '["http://localhost:8080"]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: '["GET", "HEAD", "DELETE"]'
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials: '["true"]'
    volumes:
      - registry-data:/var/lib/registry

  registry-ui:
    image: joxit/docker-registry-ui:latest
    container_name: registry-ui
    ports:
      - "8080:80"
    environment:
      - REGISTRY_TITLE=Private Docker Registry
      - REGISTRY_URL=http://localhost:5000
      - DELETE_IMAGES=true
    depends_on:
      - registry

volumes:
  registry-data:

Note: if you’re going to be running your services on port 8080, you might wish to change the UI here to use port 8081, for example.

In the above I name my container container-registry as I already have a registry container running on my machine, the REGISTRY_HTTP_HEADERS_Access were required as I was getting CORS like issues. Finally we run up the joxit/docker-registry-ui which gives us a web UI to our registry.

To run everything just type

docker-compose up

and use ctrl+c to bring this down when in interactive mode or run

docker-compose down

If you want to clear up the volume (i.e. remove it) use

docker-compose down -v

Ofcourse you can also use curl etc. to interact with the registry itself, for example to list the repositories

curl http://localhost:500/v2/_catalog

Tag and push

We’re obviously going to need to push images to the registry and this is done by first, tagging them (as usual) then pushing, so for example

docker tag putridparrot.echo_service:v1 localhost:5000/putridparrot.echo_service:v1

which tags the image I already created for a simple “echo service”.

Next we push the tagged image to the registry using

docker push localhost:5000/putridparrot.echo_service:v1

If you’re running the web UI you should be able to see the repository with your new tagged image.

Pull

Obviously we’ll want to be able to pull an image from the registry either to run locally or to deploy within a Kubernetes cluster etc.

docker pull localhost:5000/putridparrot.echo_service:v1

Deployment

If some coming posts I will be writing an echo service in multiple languages, so let’s assume this echo_services is one of those. We’re going to want to run things locally so we might have a deployments.yaml with the following deployment and service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  namespace: dev
  labels:
    app: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
  template:
    metadata:
      labels:
        app: echo
    spec:
      containers:
      - name: echo
        image: localhost:5000/putridparrot.echo_service:v1
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "100Mi"
            cpu: "100m"
          limits:
            memory: "200Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /livez
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: echo-service
  namespace: dev
  labels:
    app: echo
spec:
  type: ClusterIP
  selector:
    app: echo 
  ports:
  - name: http
    port: 80
    targetPort: 8080
    protocol: TCP

Now we can use port forwarding in place on an ingress service if we’d like, for testing, like this

kubectl port-forward svc/echo-service 8080:8080 -n dev

and now use http://localhost:8080/echo?text=Putridparrot

Other options to get this deployment running with ingress require hosts file changes or we can add a load balancer, for example

apiVersion: v1
kind: Service
metadata:
  name: echo-service
spec:
  type: LoadBalancer
  selector:
    app: echo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080