Creating a Private Docker Registry – Local Development (Part 2)

Introduction

This post is going to describe how I created a frontend for the private docker registry made in part 1.

Docker setup

uconn/express image

Because I wanted to use docker for this project, the first thing I needed to do was create another container that would run the express server. I tried to keep the image I made for this task fairly straightforward. Perhaps I'll post more technical details later, but briefly it....

  • creates a /project directory to work in
  • exposes ports at 3000 and 3001
  • installs dependencies from a package.json file
  • starts the server

 

docker-compose.yml

With this image for express, I could create a new entry in the docker-compose.yml file. The new service looks like this...

        
          server:
    image: uconn/express
    container_name: registry-server
    ports:
        - "3000:3000"
    environment:
        NODE_ENV: development
    restart: always
    volumes:
        - ./:/project        
    

All of these properties were explained in Part 1. So, the whole file looks like this -

        
          version: '3'
services:
  registry:
    image: registry:2
    container_name: registry
    ports:
      - "5000:5000"
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
    volumes:
      - ./auth:/auth
      - ./mnt/registry:/var/lib/registry
    restart: always
  server:
    image: uconn/express
    container_name: registry-server
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: development
    restart: always
    volumes:
      - ./:/project        
    

The image is pre-configured to execute the command npm run start. So any start command that is added to the package.json file will be used automatically! In this case for development, I wanted to make sure I could run both the server and webpack. Next, I'll show how I started that process.

Server setup

Before writing any code, it's important to get a few things out of the way.

  • adding dependencies for the server
  • setting up npm tasks
  • creating a few files and folders

 

Starting dependencies

To get started I wanted to make sure that I could

  • get a server running (and keep it running) and serve some static content
  • have some nice logging features
  • be able to run npm tasks in parallel
  • test a 'hello world' api route

I'll leave webpack and most of the frontend for the next section.

To start, install dependencies with the following commands

  • npm init (to set up the package.json)
  • npm install axios express
  • npm install --save-dev morgan nodemon npm-run-all

That's it!

Starting tasks

Next you need to add some tasks to the scripts section of the package.json. If there's no scripts section, that's ok. Add it in!

The scripts needed are going to:

  • use nodemon to start a server and keep it running
  • use webpack to watch our files (no, we haven't added this yet, I'll come back to it later)
  • run these tasks in parallel (which will also happen later)
        
          {
    "scripts": {
        "develop:server": "nodemon server.js",
        "develop:webpack": "webpack --watch",
        "start": "npm-run-all --parallel develop:server",
    }
}        
    

Hello server! Getting an express server started.

To start, create a server.js file at the root of your project. At the top of that file, bring in

  • axios
  • express
  • and the username/password from the .env.js file that was created by the auth script from Part 1.
        
          // server.js
const axios = require('axios')
const express = require('express')
const morgan = require('morgan')

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

Next, I like to create a section where I can keep track of everything going on in the server environment. More will be added to this section later, but for now below the require statements....

        
          /**
 * 
 * Prepare the environment
 * 
 */
const PORT = 3000;

// this will come in handy later 
// for develpoment vs production
const { env: { NODE_ENV } } = process;        
    

Below that, it's time to

  • create the express server
  • enable logging
  • and enable serving static assets

To do that, you'll create an express app and use the use method along with morgan and the express.static method.

        
          /**
 * 
 * begin working with the express server
 * 
 */

// create the express server app
const app = express();

// enable logging with morgan
app.use(morgan('combined'));

// serve assets from the /public directory
app.use(express.static('public'));        
    

The express.static method will now serve any static assets inside the public folder. So anything you want visible in the browser should go there.

The last thing you need to do is actually start the server. Because right now it isn't doing anything. To do that, at the very end of the server.js file, add:

        
          /**
 * 
 * begin listening to the app on port 3000
 * 
 */
app.listen(PORT, () => {
    console.log(`listening on ${PORT}`)
});        
    

Now when the docker container is run, the server will automatically be built and begin listening and serving files from the public folder on localhost:3000. At this point, the whole 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');

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

Conclusion

At this point, the container is ready to serve files because the host port and directory are mapped to the docker container. But there's still a problem.

The front end can't yet communicate with the registry container that has all the images.

So that's the next post! I'll get into a little bit of docker networking and sending requests from the browser to the registry container and back.

Posted by Adam Berkowitz