Merge pull request #132 from sixeyed/master

Add registry Windows lab
This commit is contained in:
Elton Stoneman 2016-11-28 20:58:16 +00:00 коммит произвёл GitHub
Родитель 5d23b0a01f 5fe24549f4
Коммит 0976d42e73
8 изменённых файлов: 581 добавлений и 0 удалений

Просмотреть файл

@ -6,3 +6,4 @@ We have three Windows and .NET tutorials
* [Beginning ASP.NET Web application](aspnet-web/README.md)
* [Hello Dotnetbot App](hello-dotnetbot/README.md)
* [SQL Server Database](sql-server/README.md)
* [Run a local Docker Registry](registry/README.md)

Просмотреть файл

@ -0,0 +1,11 @@
# escape=`
FROM microsoft/nanoserver
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]
EXPOSE 5000
ENV REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=c:\\data
WORKDIR c:\\registry
COPY ./registry/ .
CMD ["registry", "serve", "config.yml"]

Просмотреть файл

@ -0,0 +1,13 @@
# escape=`
# REMARK - when Go 1.8 is released we can use an official image for the base.
# There is an issue with Go 1.7 and Windows 2016 SYMLINKD directories which causes Docker volumes and host mounts to fail.
# This registry build uses a custom build of Go (@a2bd5c5) from the master branch to fix - https://github.com/golang/go/issues/15978
FROM sixeyed/golang:windowsservercore
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]
CMD .\go get github.com/docker/distribution/cmd/registry ; `
cp \"$env:GOPATH\bin\registry.exe\" c:\out\ ; `
cp \"$env:GOPATH\src\github.com\docker\distribution\cmd\registry\config-example.yml\" c:\out\config.yml

Просмотреть файл

@ -0,0 +1,33 @@
# Registry Lab
A registry is a service for storing and accessing Docker images. [Docker Hub](https://hub.docker.com) and [Docker Store](https://store.docker.com) are the best-known hosted registries, which you can use to store public and private images. You can also run your own registry using the open-source [Docker Registry](https://docs.docker.com/registry), which is a Go application that can run in a Windows container.
## What You Will Learn
You'll learn how to:
- build a Docker image which packages the [Docker Registry](https://docs.docker.com/registry) application on Windows Nano Server;
- run a local registry in a container and configure your Docker engine to use the registry;
- generate SSL certificates (using Docker!) and run a secure local registry with a friendly domain name;
- generate encrypted passwords (using Docker!) and run an authenticated, secure local registry over HTTPS with basic auth.
> Note. The open-source registry does not have a Web UI, so there's no friendly interface like [Docker Hub](https://hub.docker.com) or [Docker Store](https://store.docker.com). Instead there is a [REST API](https://docs.docker.com/registry/spec/api/) you can use to query the registry. For a local registry which has a Web UI and role-based access control, Docker, Inc. has the [Trusted Registry](https://www.docker.com/sites/default/files/Docker%20Trusted%20Registry.pdf) product.
### Prerequisites
You'll need Docker running on Windows. You can follow the [Windows Container Lab Setup](https://github.com/docker/labs/blob/master/windows/windows-containers/Setup.md) to install Docker on Windows 10, or Windows 2016 - locally, on AWS and Azure.
You should be familiar with the key Docker concepts, and with Docker volumes:
- [Docker concepts](https://docs.docker.com/engine/understanding-docker/)
- [Docker volumes](https://docs.docker.com/engine/tutorials/dockervolumes/)
## The Lab
- [Part 1 - Building the Registry Image](part-1.md)
- [Part 2 - Running a Registry Container](part-2.md)
- [Part 3 - Running a Secured Registry Container](part-3.md)
- [Part 4 - Using Basic Authentication with a Secured Registry](part-4.md)

102
windows/registry/part-1.md Normal file
Просмотреть файл

@ -0,0 +1,102 @@
# Part 1 - Building the Registry Image
Docker provides an [official registry image](https://hub.docker.com/_/registry/) on the Hub, but currently it is only available as a Linux image. The application is written in Go so it can be compiled for Windows and run as a container on Windows 10 and Windows Server 2016.
> Note. Expect the official image to have a Windows variant soon, but this part of the lab is still useful if you want to build from the latest source code yourself.
## Building from Source
The Go source code for the registry is on GitHub in Docker's [distribution](https://github.com/docker/distribution) repository. Building Go applications is simple with the [go command](https://golang.org/doc/articles/go_command.html), we just need to install the Go tools and run:
```PowerShell
go get github.com/docker/distribution/cmd/registry
```
On Windows the build will produce a binary file, `registry.exe`, which is a standalone executable. We can package that exe into a Docker image which doesn't need Go installed, so our Windows registry image will be small and focused.
We won't install Go and Git on our local machine though, instead we'll use the [Docker Builder pattern](http://blog.terranillius.com/post/docker_builder_pattern/) and package up the toolset into an image we can use to build the registry on any Windows host running Docker.
## Dockerfile for the Registry Builder
The Dockerfile for the registry builder is in [Dockerfile.builder](Dockerfile.builder), and it's very simple. It begins in the usual way for Windows Dockerfiles - overriding the escape character and specifying PowerShell to use as the command shell:
```Dockerfile
# escape=`
FROM sixeyed/golang:windowsservercore
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]
```
The base image is [sixeyed/golang](https://hub.docker.com/r/sixeyed/golang/) which is on the Docker Hub, built from [this Dockerfile](https://github.com/sixeyed/dockers-windows/blob/master/golang/Dockerfile). It has the Go toolset installed, but rather than using a released version, it builds Go from the source on GitHub to get the latest features.
> Note. There is an [official Go image](https://hub.docker.com/_/golang/) on Docker Hub, which has Windows Server Core and Nano Server variants. That uses the latest release of Go - 1.7 - which has [an issue](https://github.com/golang/go/issues/15978) that stops you using Docker volumes on Windows. It's fixed in the latest source code, which is why we use an image that builds from source, but when Go 1.8 is released we can switch to using the official image as the base for the builder.
There's a single `CMD` instruction to build the latest version of the registry and copy the built files to a known output location:
```Dockerfile
CMD .\go get github.com/docker/distribution/cmd/registry ; `
cp \"$env:GOPATH\bin\registry.exe\" c:\out\ ; `
cp \"$env:GOPATH\src\github.com\docker\distribution\cmd\registry\config-example.yml\" c:\out\config.yml
```
When we run a container from the builder image, it will compile the latest registry code, and we can map the output location to a directory on the host to get the application files.
## Building the Registry Server
First we build the builder image - all the images in this lab use [microsoft/windowsservercore](https://hub.docker.com/r/microsoft/windowsservercore/) or [microsoft/nanoserver](https://hub.docker.com/r/microsoft/nanoserver/) as the base images, so you can only use a Windows host to build and run them. From a PowerShell session, navigate to the lab folder and build the builder:
```PowerShell
docker build -t registry-builder -f Dockerfile.builder .
```
When you first run this command, Docker will download the `sixeyed/golang` image, which will take a while. When it's done, you can create a directory for the application, and run a builder container to build the application and copy it to the host:
```PowerShell
mkdir registry
docker run --rm -v $pwd\registry:c:\out registry-builder
```
Now you'll have two files in the `.\registry` folder in the lab root:
- `registry.exe` - the standalone registry server application (around 18MB);
- `config.yml` - a basic configuration file for running a local registry (<1KB).
These are the files we'll package into a registry server image.
## Dockerfile for the Registry Server
The [Dockerfile](Dockerfile) for the registry image is also very simple. It starts in the standard way and uses `microsoft/nanoserver` as the base, because we don't need any of the additional features in `microsoft/windowsservercore`:
```Dockerfile
# escape=`
FROM microsoft/nanoserver
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]
```
Next we set up the integration between the container and the host:
```Dockerfile
EXPOSE 5000
ENV REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=c:\\data
```
Port 5000 is the default port for the registry, so we make that available from the image, and we specify a value for the `REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY` environment variable. That's where the registry stores all the image layers, and we default to a known location, `c:\data`.
Finally we copy the applcation files into the working directory and set the startup command:
```Dockerfile
WORKDIR c:\\registry
COPY ./registry/ .
CMD ["registry", "serve", "config.yml"]
```
To build the image, we just need to run `docker build`:
```PowerShell
docker build -t registry .
```
The Docker image for the registry running on Nano Server is 310MB compressed and 830MB uncompressed - of which 810MB is the `microsoft/nanoserver` base layer.
## Next
- [Part 2 - Running a Registry Container](part-2.md)

153
windows/registry/part-2.md Normal file
Просмотреть файл

@ -0,0 +1,153 @@
# Part 2 - Running a Registry Container
There are several ways to run a registry container from the image we built in [Part 1](part-1.md). The simplest is to run an insecure registry over HTTP, but for that we need to configure Docker to explicitly allow insecure access to the registry.
## Testing the Registry Image
First we'll test that the registry image is working correctly, by running it without any special configuration:
```PowerShell
docker run -d -p 5000:5000 --name registry registry
```
To use the registry we'll need the IP address of the container:
```PowerShell
> $ip = docker inspect --format '{{ .NetworkSettings.Networks.nat.IPAddress }}' registry
> $ip
172.24.194.96
```
In my case, the container IP address is `172.24.194.96`, yours will be different. We'll use that as the domain part of the image name for pushing and pulling images.
## Understanding Image Names
Typically we work with images from the Docker Hub, which is the default registry for the Docker Engine. Commands using just the image repository name work fine, like this:
```PowerShell
docker pull microsoft/nanoserver
```
`microsoft/nanoserver` is the repository name, which we are using as a short form of the full image name. The full name is `docker.io/microsoft/nanoserver:latest`. That breaks down into three parts:
- `docker.io` - the hostname of the registry which stores the image;
- `microsoft/nanoserver` - the repository name, in this case in `{userName}/{imageName}` format;
- `latest` - the image tag.
If a tag isn't specified, then the default `latest` is used. If a registry hostname isn't specified then the default `docker.io` for Docker Hub is used. If you want to use images with any other registry, you need to explicitly specify the hostname - the default is always Docker Hub, you can't change to a different default registry.
With our local registry, the hostname is the IP address, and we also need to specify the custom port we're using. The full registry address is `172.24.194.96:5000`.
## Configuring Docker for the Insecure Registry
Docker expects all registries to run on HTTPS. In the next part of this lab we will run a secure version of our registry container, but the current version runs on HTTP. If you try to use it, Docker will give you an error message like this:
```
http: server gave HTTP response to HTTPS client
```
We need to set up the Docker Engine to explictly use HTTP for our insecure registry. The setting is needed for `dockerd.exe`, which is the Windows version of the Docker engine. In Windows it runs as a Windows Service, so first we need to stop and unregister the service.
> Note. This will kill all your running containers.
```PowerShell
Stop-Service docker
dockerd --unregister-service
```
Now we'll re-register the service with custom startup options, adding the IP address of the registry container as an insecure registry, where the engine will use HTTP rather than HTTPS:
```
dockerd --register-service -G docker -H npipe:// -H 0.0.0.0:2375 --insecure-registry 172.24.194.96:5000
Start-Service docker
```
Docker is running again now, so we can restart the registry container. When we stopped the Docker Windows Service, the container was stopped, but it still exists with the original IP address, so we can just start it again:
```PowerShell
docker start registry
```
The registry is running at the expected address, and we've configured Docker to allow access, so we can push and pull images to our local registry, just like we can with Docker Hub and Docker Store.
## Pushing and Pulling from the Local Registry
Docker uses the hostname from the full image name to determine which registry to use. We can buid images and include the local registry hostname in the image tag, or use the `docker tag` command to add a new tag to an existing image.
These commands pull a public image from Docker Hub, tag it for use in the private registry with the full name `172.24.194.96:5000/labs/hello-world:nanoserver`, and then push it to the registry:
```PowerShell
docker pull sixeyed/hello-world:nanoserver
docker tag sixeyed/hello-world:nanoserver 172.24.194.96:5000/labs/hello-world:nanoserver
docker push 172.24.194.96:5000/labs/hello-world:nanoserver
```
When you push the image to your local registry, you'll see similar output to when you push a public image to the Hub:
```
The push refers to a repository [172.24.194.96:5000/labs/hello-world]
d6826c28b1cd: Pushed
2c195a33d84d: Skipped foreign layer
342d4e407550: Skipped foreign layer
nanoserver: digest: sha256:961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4 size: 1149
```
> Note. The two layers from Microsoft's base image are skipped - they don't get stored in the local registry, because the image is not freely redistributable. Check [GitHub issue 27580]( https://github.com/docker/docker/issues/27580) for more information.
On the local machine, you can remove the new image tag and the original image, and pull it again from the local registry to verify it was correctly stored:
```
docker rmi 172.24.194.96:5000/labs/hello-world:nanoserver
docker rmi sixeyed/hello-world:nanoserver
docker pull 172.24.194.96:5000/labs/hello-world:nanoserver
```
That exercise shows the registry we built works correctly, but at the moment it's not very useful because all the image data is stored in the container's writeable storage area, which will be lost when the container is removed. To store the data outside of the container, we need to mount a host directory when we start the container.
## Running a Registry Container with External Storage
Let's get rid of the existing registry container - removing the container also removes its storage layer, so any images you had pushed will be lost:
```PowerShell
docker kill registry
docker rm registry
```
We'll use a host-mounted Docker volume for the new container. When the registry server in the container writes image layer data, it will think it's writing to a local directory in the container but it will actually be writing to a folder on the host.
We also need to specify the IP address, because we want to use the same IP the previous registry had - that's what our Docker Engine is configured to use, and it's how we've tagged our images. The `-v` option maps the host folder, and the `--ip` option specifies an IP address:
```PowerShell
mkdir c:\registry-data
docker run -d -p 5000:5000 --name registry -v c:\registry-data:c:\data --ip 172.24.194.96 registry
```
Repeat the previous `docker push` command to upload an image to the registry container, and the layers will be stored in the container's `C:\data` directory, which is actually mapped to the `C:\registry-data` directory on you local machine. The `tree` command will show you the directory structure the registry server uses:
```
> &tree c:\registry-data
Folder PATH listing for volume Windows 2016
Volume serial number is A456-8058
C:\REGISTRY-DATA
└───docker
└───registry
└───v2
├───blobs
│ └───sha256
│ ├───06
│ │ └───06162e188174e2f0b76a2fd507645ca13b3beb43204cccddf82dcb0251e34fb4
│ ├───96
│ │ └───961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4
...
```
Storing data outside of the container means we can build a new version of the registry image and replace the old container with a new one using the same host mapping - so the new registry container has all the images stored by the previous container.
This container is still limited though. The container is accessible externally using the host's IP address, but that's different from the container's IP address - so you need to use different tags and a different engine configuration for external clients.
Using an insecure registry also isn't practical in multi-user scenarios. Effectively there's no security so anyone can push and pull images if they know the registry hostname. The registry server supports authentication, but only over a secure SSL connection. We'll run a secure version of the registry server in a container next.
## Next
- [Part 3 - Running a Secured Registry Container](part-3.md)

154
windows/registry/part-3.md Normal file
Просмотреть файл

@ -0,0 +1,154 @@
# Part 3 - Running a Secured Registry Container
We saw how to run a simple registry container in [Part 2](part-2.md), using the image we built in [Part 1](part-1.md). The registry server con be configured to serve HTTPS traffic on a known domain, so it's straightforward to run a secure registry for private use with a self-signed SSL certificate.
## Generating the SSL Certificate
The Docker docs explain how to [generate a self-signed certificate](https://docs.docker.com/registry/insecure/#/using-self-signed-certificates) on Linux using a command like this:
```
openssl req \
-newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \
-x509 -days 365 -out certs/domain.crt
```
[OpenSSL](https://www.openssl.org/) is a very popular TLS/SSL toolkit in Linux, but it's less common in Windows. There is a Windows build hosted at [indy.fulgan.com](https://indy.fulgan.com/SSL/), but rather than install it onto our Windows host to run a one-off command, we can use a Docker image with OpenSSL installed.
The [sixeyed/openssl](https://hub.docker.com/r/sixeyed/openssl/) image on Docker Hub is built from this [Dockerfile](https://github.com/sixeyed/dockers-windows/blob/master/openssl/Dockerfile), which just installs and configures OpenSSL on top of the Windows Nano Server base image.
We can use it to generate the SSL certificate for the registry with Docker:
```PowerShell
mkdir certs
docker run -it --rm -v $pwd\certs:c:\certs sixeyed/openssl:nanoserver `
req -newkey rsa:4096 -nodes -sha256 -x509 -days 365 `
-keyout c:\certs\registry.local.key -out c:\certs\registry.local.crt `
-subj '/CN=registry.local/O=sixeyed/C=GB'
```
This will create a certificate file `registry.local.crt`, and a private key file `registry.local.key` in the `certs` subdirectory of the working path. The `-subj` option specifies the details for the domain. This example uses `registry.local` for the common name, which needs to match the hostname address for the registry server. The organization name and country aren't used, but need to be valid values.
Now we have an SSL certificate, we can run a secure registry.
## Running the Registry Securely
The registry server supports several configuration switches as environment variables, including the details for running securely. We can use the same image we've already used, but configured for HTTPS.
If you have an insecure registry container still running from [Part 2](part-2.md), remove it:
```PowerShell
docker kill registry
docker rm registry
```
For the secure registry, we need to run a container which has the SSL certificate and key files available, which we'll do with an additional volume mount (so we have one volume for registry data, and one for certs). We also need to specify the location of the certificate files, which we'll do with environment variables:
```PowerShell
docker run -d -p 5000:5000 --name registry `
-v c:\registry-data:c:\data -v $pwd\certs:c:\certs `
-e REGISTRY_HTTP_TLS_CERTIFICATE=c:\certs\registry.local.crt `
-e REGISTRY_HTTP_TLS_KEY=c:\certs\registry.local.key `
registry
```
The new parts to this command are:
- `-v $pwd\certs:c:\certs` - mount the local `certs` folder into the container, so the registry server can access the certificate and key files;
- `-e REGISTRY_HTTP_TLS_CERTIFICATE` - specify the location of the SSL certificate file;
- `-e REGISTRY_HTTP_TLS_KEY` - specify the location of the SSL key file.
We'll let Docker assign a random IP address to this container, because we'll be accessing it by host name. The registry is running securely now, but we've used a self-signed certificate for an internal domain name, so we need to set up Windows to find the host and trust the certificate.
## Configuring Windows to Access the Registry
The Docker client uses the security of the host operating system when it accesses an HTTPS registry. Windows doesn't trust self-signed certificates because they weren't created by a trusted Certificate Authority, so Windows will block access to our secure registry.
In our network we can trust our own certificate, but we'll need to add it to the certificate store in every Windows client machine we want to use with the registry. The `registry.local.crt` file we generated is the public certificate which can be safely distributed - the `registry.local.key` file is the private key which should be kept secure.
These PowerShell commands will install the certificate onto the Windows host:
```PowerShell
$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 `
$pwd\certs\registry.local.crt
$store = new-object System.Security.Cryptography.X509Certificates.X509Store('Root','localmachine')
$store.Open('ReadWrite')
$store.Add($cert)
$store.Close()
```
> Note. This installs the self-signed certificate as a trusted CA for all users of the machine. If the private key for your cert is compromised then an attacker could exploit the trust you set up here, by signing malicious services with your certificate.
If you want to use the registry from other Windows machines, you'll need to distribute the `.crt` fike (**not** the `.key` file), and install the certificate on all client machines.
The next step is to add a DNS entry for the `registry.local` hostname to point to the container's IP address. The easiest way to do that is by adding an entry to the [hosts](https://en.wikipedia.org/wiki/Hosts_(file)) file:
```PowerShell
Add-Content -Path 'C:\Windows\System32\drivers\etc\hosts' "$ip registry.local"
```
Remote machines will be able to access the registry container from your Docker host, because we publish the port when the container starts - but the hosts entry will be for the IP address of the machine, not the container.
## Accessing the Secure Registry
We're ready to run a secure registry now. We want a reliable service, so we'll remove the existing container and start a new one with some more options:
```PowerShell
docker kill registry
docker rm registry
docker run -d -p 5000:5000 --name registry `
--ip $ip --restart unless-stopped `
-v c:\registry-data:c:\data -v $pwd\certs:c:\certs `
-e REGISTRY_HTTP_TLS_CERTIFICATE=c:\certs\registry.local.crt `
-e REGISTRY_HTTP_TLS_KEY=c:\certs\registry.local.key `
registry
```
The new parts here are:
- `--ip $ip` - specify an explicit IP adress. We're using the IP from the previous container, which is what the address we've mapped to the `registry.local` domain name in the `hosts` file;
- `--restart unless-stopped` - restart the container when it exits, unless it has been explicity stopped. When the host restarts, Docker will start the registry container, so it's always available.
Now we have a domain name for our registry, the image tags are a lot more flexible - we don't need a specific IP address in the image name. We still tag, push and pull images in the same way:
```PowerShell
docker tag sixeyed/hello-world:nanoserver registry.local:5000/labs/hello-world
docker push registry.local:5000/labs/hello-world
```
> Note. You can use any valid DNS name for your registry hostname but there must be at least one period in the name - if there's no period then Docker can't distinguish the hostname part from the repository name. The hostname you use has to match the common name you used when you generated the SSL certificate.
The IP address for my Docker host is `192.168.2.196`, so on a *different* machine I can map the registry hostname to that address by writing to the host file, and install the certificate which I've copied locally:
```PowerShell
Add-Content -Path 'C:\Windows\System32\drivers\etc\hosts', "192.168.2.196 registry.local"
$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 `
c:\drops\registry.local.crt
$store = new-object System.Security.Cryptography.X509Certificates.X509Store('Root','localmachine')
$store.Open('ReadWrite')
$store.Add($cert)
$store.Close()
```
And from that machine (with Docker installed), I can pull the image from the registry container running on the remote machine on my local network:
```PowerShell
> docker pull registry.local:5000/labs/hello-world
Unable to find image 'registry.local:5000/labs/hello-world:latest' locally
latest: Pulling from labs/hello-world
5496abde368a: Already exists
94b4ce7ac4c7: Pull complete
06162e188174: Pull complete
Digest: sha256:961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4
Status: Downloaded newer image for registry.local:5000/labs/hello-world:latest
```
In this case, the client machine already had one of the Windows Nano Server base layers (`5496a`), but it pulled an update layer from Docker Hub (`94b4c`), and it pulled the custom layer for my image from my own registry (`06162`).
We can go one step further with the open-source registry server, and add basic authentication - so we can require users to securely log in to push and pull images.
## Next
- [Part 4 - Using Basic Authentication with a Secured Registry](part-4.md)

114
windows/registry/part-4.md Normal file
Просмотреть файл

@ -0,0 +1,114 @@
# Part 4 - Using Basic Authentication with a Secured Registry
From [Part 3](part-3.md) we have a registry running in a Docker container, which we can securely access over HTTPS from any machine in our network. We used a self-signed certificate, which has security implications, but you could buy an SSL from a CA instead, and use that for your registry. With secure communication in place, we can set up user authentication.
## Usernames and Passwords
The registry server and the Docker client support [basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) over HTTPS. The server uses a file with a collection of usernames and encrypted passwords. The file uses a common standard from the Linux world - [Apache htpasswd](https://httpd.apache.org/docs/current/programs/htpasswd.html), but as usual we don't want to install Apache on our local machine to use one tool.
On the Docker Hub, the repository [sixeyed/httpd](https://hub.docker.com/r/sixeyed/httpd/) ([Dockerfile](https://github.com/sixeyed/dockers-windows/blob/master/httpd/Dockerfile)) is configured with Apache and the associated tools already installed. We can use that image to run `htpasswd` and generate the encrypted strings. These commands create a new `auth` subdirectory and a new `registry.htpasswd` file with one set of credentials:
```PowerShell
mkdir auth
$creds = docker run --rm sixeyed/httpd:windowsservercore htpasswd -b -n -B elton d0cker
Add-Content -Path .\auth\registry.htpasswd $creds[0]
```
> Note. The output from `htpasswd` contains additional whitespace which we need to remove - we only write the first line of output in the text file.
The [htpasswd options](https://httpd.apache.org/docs/current/programs/htpasswd.html) will create the encrypted password in a suitable format for the registry server:
- `-b` - use bcrypt encryption
- `-n` - read username and password from the command line
- `elton` - username
- `d0cker` - password.
To add new users, repeat the command to append a new entry to the existing file. This adds a new user `francis`, with password `r3gL4b`:
```PowerShell
$creds = docker run --rm sixeyed/httpd:windowsservercore htpasswd -nb -B francis r3gL4b
Add-Content -Path .\auth\registry.htpasswd $creds[0]
```
We can verify the entries have been written by checking the file contents - which should show the usernames in plain text and a cipher text password:
```PowerShell
> cat .\auth\registry.htpasswd
elton:$2y$05$saIVQ.pV.9EPOsLpNP04puj0Mf9r2wMHeGg/XViGhPfdoxb1oaCPO
francis:$2y$05$CWobimW8aoKSCLf4cp9lGulzAXxPfUIqc452PdArxPfyK8zPEOG9a
```
## Running an Authenticated Secure Registry
Adding authentication to the registry is a similar process to adding SSL - we need to run the registry with access to the `htpasswd` file on the host, and configure authentication using environment variables.
As before, we'll remove the existing container and run a new one with authentication configured:
```PowerShell
docker kill registry
docker rm registry
docker run -d -p 5000:5000 --name registry `
--ip $ip --restart unless-stopped `
-v c:\registry-data:c:\data -v $pwd\certs:c:\certs -v $pwd\auth:c:\auth `
-e REGISTRY_HTTP_TLS_CERTIFICATE=c:\certs\registry.local.crt `
-e REGISTRY_HTTP_TLS_KEY=c:\certs\registry.local.key `
-e REGISTRY_AUTH=htpasswd `
-e REGISTRY_AUTH_HTPASSWD_REALM='Registry Realm' `
-e REGISTRY_AUTH_HTPASSWD_PATH=c:\auth\registry.htpasswd `
registry
```
The new options for this container are:
- `-v $pwd\auth:c:\auth` - mount the local `auth` folder into the container, so the registry server can access `htpasswd` file;
- `-e REGISTRY_AUTH=htpasswd` - use the registry's `htpasswd` authentication method;
- `-e REGISTRY_AUTH_HTPASSWD_REALM='Registry Realm'` - specify the authentication realm;
- `-e REGISTRY_AUTH_HTPASSWD_PATH=c:\auth\registry.htpasswd` - specify the location of the `htpasswd` file.
Now the registry is using secure transport and user authentication.
## Authenticating with the Registry
With basic authentication, users cannot push or pull from the registry unless they are authenticated. If you try and pull an image without authenticating, you will get an error:
```PowerShell
> docker pull registry.local:5000/labs/hello-world
Using default tag: latest
Error response from daemon: Get https://registry.local:5000/v2/labs/hello-world/manifests/latest: no basic auth credentials
```
The result is the same for valid and invalid image names, so you can't even check a repository exists without authenticating. Logging in to the registry is the same `docker login` command you use for Docker Hub, specifying the registry hostname:
```PowerShell
> docker login registry.local:5000
Username: elton
Password:
Login Succeeded
```
If you use the wrong password or a username that doesn't exist, you get a `401` error message:
```
Error response from daemon: login attempt to https://registry.local:5000/v2/ failed with status: 401 Unauthorized
```
Now you're authenticated, you can push and pull as before:
```PowerShell
> docker pull registry.local:5000/labs/hello-world
Using default tag: latest
latest: Pulling from labs/hello-world
Digest: sha256:961497c5ca49dc217a6275d4d64b5e4681dd3b2712d94974b8ce4762675720b4
Status: Image is up to date for registry.local:5000/labs/hello-world:latest
```
> Note. The open-source registry does not support the same authorization model as Docker Hub or Docker Trusted Registry. Once you are logged in to the registry, you can push and pull from any repository, there is no restriction to limit specific users to specific repositories.
## Conclusion
[Docker Registry](https://docs.docker.com/registry/) is a free, open-source application for storing and accessing Docker images. You can run the registry in a container on your own network, or in a virtual network in the cloud, to host private images with secure access. For Linux hosts, there is an [official registry image](https://hub.docker.com/_/registry/) on Docker Hub, but in this lab we saw how to build and run the registry from the lastest source code, in a Windows container.
We've covered all the options, from running an insecure registry, through adding SSL to encrypt traffic, and finally adding basic authentication to restrict access. By now you know how to set up a usable registry in your own environment, and you've also used some key Docker patterns - using containers as build agents and to run basic commands, without having to install software on your host machines.
There is still more you can do with Docker Registry - using a different [storage driver](https://docs.docker.com/registry/storage-drivers/) so the image data is saved to reliable share storage, and setting up your registry as a [caching proxy for Docker Hub](https://docs.docker.com/registry/recipes/mirror/) are good next steps.