Creating a Private Docker Registry – APIs/docker networks (Part 3)

Introduction

One of the great things about using docker-compose is that the containers are automatically networked together even if they're not exposed to the outside world. So this post is going to go over

  • how that works in the registry app and
  • what that means to the browser

Let's go!!!!!!!!!

 

Docker networking

First things first....

Open the terminal and run docker-compose up to start the containers if they're not already running.

If you don't have any images in the private registry go add some by following the directions in part 1.

Done? Great!

The Docker registry has an API. It's (in my humble opinion) a little odd, but that's ok. We're only going to need two things from it

  • a list of images
  • a list of tags for each image

The route for the list of all images (called the catalog) is at /v2/_catalog. So in the browser you can go to localhost:5000/v2/_catalog. You should get a little popup asking for the username and password you created in part 1.

You can also use postman to check the API. To do that though, you need to pass the username/password like this.

A screen shot of passing credentials to the registry via postman

If everything works, you'll get back a json response like this

        
          // json response
{
    "repositories": [
        "hello/world",
        "uconn/express"
    ]
}        
    

This proves that data will come back from the registry and you can then do something with it. But there's a problem.

The express server doesn't know what localhost:5000 is.

I can't begin to tell you how long this confused me for. When the express server wants to talk with the registry, it doesn't do it on localhost, it needs to go through registry which is the name of the service the registry container is in.

But why does this even matter? The browser could get information back from localhost:5000/v2/_catalog. Can't the client application (our frontend site) access that directly?

NOPE!

The static assets for the client application are hosted inside the express container so they can never hit that route.

Instead, what you need to do is create a.....

express Proxy Server

Imagine the chain of information starting at the browser window for a moment. You want to get information from the registry so what do you do?

  • make an API request to the express server
  • have the server make an API request through the docker network to the registry container
  • get the data from the registry
  • return it to the express server
  • pass it back to the browser

What does that look like? Well I'll show you!

Express API

Fortunately for this application we really only need one (or two depending how you look at it) endpoint(s).

I want information about docker images, so I'm going to call it /api/images.

Express has a get method. When the browser makes a GET request to the endpoint we define, express will execute a callback function. This is where the proxy will happen.

The credentials need to be passed to the registry on all the requests that are made to it. axios helps with that by letting you set some default parameters for requests.

        
          /*
*
* begin creating the API
* 
* set the default (basic) authorization for all requests.
* this authorization is stored in the container at /auth/htpasswd
*
*/
axios.defaults.auth = {
  username: USERNAME,
  password: PASSWORD
}

/**
 * get all images in the registry
 */

app.get('/api/images', async (req, res) => {
    // registry in the url is the name of the service
    // await the resolved or rejected promise
    // axios makes a request within the docker network
    await axios.get(`http://registry/v2/_catalog`)
    .then((response) => {
        // return the catalog as a json object on success
        res.json(response.data);
    })
    .catch((err) => {
        // catch errors and log them.
        console.log('Axios error -> images ', err);
        return err;
    })
})        
    

At this point, you can imagine what the flow of data will be based on the list that got made above.

  • a request to /api/images starts at the browser
  • that request is intercepted by express and sent to app.get()
  • that starts an asynchronous callback function which...
  • uses axios to make a different http request to the internal docker network
  • axios awaits either the response or catches an error
  • for successful responses, the result is returned to the browser by express

 

Conclusion

At this point, your server.js file should look like this:

        
          const axios = require('axios');
const express = require('express');
const morgan = require('morgan');

const { USERNAME, PASSWORD } = require('./.env');

const PORT = 3000;

const { env: { NODE_ENV } } = process;

const app = express();

app.use(morgan('combined'));

app.use(express.static('public');

/*
*
* begin creating the API
* 
* set the default (basic) authorization for all requests.
* this authorization is stored in the container at /auth/htpasswd
*
*/
axios.defaults.auth = {
  username: USERNAME,
  password: PASSWORD
}

/**
 * get all images in the registry
 */

app.get('/api/images', async (req, res) => {
    // registry in the url is the name of the service
    // await the resolved or rejected promise
    // axios makes a request within the docker network
    await axios.get(`http://registry/v2/_catalog`)
    .then((response) => {
        // return the catalog as a json object on success
        res.json(response.data);
    })
    .catch((err) => {
        // catch errors and log them.
        console.log('Axios error -> images ', err);
        return err;
    })
})

app.listen(PORT, () => {
    console.log(`listening on ${PORT}`)
})        
    

The next post will deal with how to get information about individual images in the registry.

Posted by Adam Berkowitz