Category: Containerized Development and Deployment

Docker and Windows — Unable to Allocate Port

On the most recent iteration of Windows (20H2 build 19042.1052) and Docker Desktop (20.10.7 built Wed Jun 2 11:54:58 2021), I found myself unable to launch my Oracle container. The error indicated that the binding was forbidden.


C:\WINDOWS\system32>docker start oracleDB
Error response from daemon: Ports are not available: listen tcp bind: An attempt was made to access a socket in a way forbidden by its access permissions.
Error: failed to start containers: oracleDB

Forbidden by whom?! Windows, it seems. Checking excluded ports using netsh:

netsh int ipv4 show excludedportrange protocol=tcp

Shows that there are all sorts of ports being forbidden — Hyper-V is grabbing a lot of ports when it starts. To avoid that, you’ve got to add a manual excluded port for the one you want to use.

To reserve the port for your own use, disable Hyper-V (reboot), add a port exclusion, and enable Hyper-V (reboot)

REM Disable Hyper-V
dism.exe /Online /Disable-Feature:Microsoft-Hyper-V 
REM REBOOT ... then add an exclusion for the Oracle DB Port
netsh int ipv4 add excludedportrange protocol=tcp startport=1521 numberofports=1 
dism.exe /Online /Enable-Feature:Microsoft-Hyper-V /All

Now 1521 is reserved for Oracle

Docker – List Container Startup Policies

A quick one-line Bash command to output all containers and the startup policy:

docker container ls -aq | xargs docker container inspect --format '{{ .Name }}: {{.HostConfig.RestartPolicy.Name}}'

For Docker on Windows, the following PowerShell command produces similar results:

$jsonData = docker container ls -aq |%{docker container inspect --format "{{json .}}"$_}
[System.Collections.ArrayList]$arrayContainerConfig = @()
foreach($jsonContainerConfig in $jsonData ){
	$psobjContainerConfig = ConvertFrom-JSON $jsonContainerConfig

$arrayContainerConfig | ForEach {[PSCustomObject]$_} | Format-Table -AutoSize

ADO Notifications

I’ve been underwhelmed with the notifications I get from Azure DevOps – there are a lot of build-centric notifications, but I don’t use ADO for builds or deployment apart from playing around. And I really don’t care if the silly test project I set up to build and deploy a website worked, failed, or whatever. I was thinking about hooking whatever they’re calling Flow this week up to ADO and building notification workflows.

Fortunately, a coworker mentioned that you can customize notifications in ADO … which, I’d spent a few seconds poking around and didn’t see anything. But I spent more than a few seconds this time and happened across this little ellipsis on the card that pops up when I click the circle with my initials in it. More options!

A new menu flies out; and, look, there’s “Notifications”

Exactly as I’ve observed, there are a lot of build-centric alerts. So I created a new subscription.

Here’s a subscription that I hope will notify me when items assigned to me have updates to activity or comments.

Docker for Windows: Using Built-in k8s

I just started using k8s built into Docker for Windows, but I couldn’t connect because the target machine actively refused the connection.

C:\Users\lisa>kubectl version
Client Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.6", GitCommit:"96fac5cd13a5dc064f7d9f4f23030a6aeface6cc", GitTreeState:"clean", BuildDate:"2019-08-19T11:13:49Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"windows/amd64"}
Unable to connect to the server: dial tcp [::1]:8080: connectex: No connection could be made because the target machine actively refused it.

No idea — it’s all internal traffic, but I resorted to turning off my firewall anyway just to see what would happen. Nothing. Turns out I need a KUBECONFIG environment variable pointing to the config file

C:\Users\lisa>set | grep KUB

Applied the yaml file and started the proxy

C:\Users\lisa>kubectl apply -f
secret/kubernetes-dashboard-certs created
serviceaccount/kubernetes-dashboard created created created
deployment.apps/kubernetes-dashboard created
service/kubernetes-dashboard created

C:\Users\lisa>kubectl proxy
Starting to serve on

Working! Get the token from

kubectl -n kube-system describe secret default

And access the dashboard.


A reminder for myself — the totally not obvious package name for the kubeadm binary on the CHANGELOG link list is Node. Go figure!

Docker Hub

Docker Official Images: Official images won’t have a publisher listed, and they will be tagged with “Official Image”.

Docker Official Images are a set of images curated by Docker. They’re generally recommended for new users as Docker has a team that reviews and publishes these images. Beyond Docker’s verification, you can see how an individual image was built. Navigate to the GitHub image library. Find the file corresponding with the image, and you’ll see a GitRepo line. Navigate to that URL to find the Dockerfiles that were used to build the image.

Docker Certified: Other images will be certified by Docker – these are published by someone other than Docker but have been tested & scanned for vulnerabilities, they come from a reputable source, and comply with best practice guidelines.

If you click on the hyperlinked organization name, they are listed as a verified publisher – this means someone put a little effort into ensuring “Oracle” is actually the corporation everyone thinks of when they hear “Oracle”

Other Images: You’ll also find containers that are not certified that have been published by un-verified parties. Don’t use these without some investigation.

We happen to interact with OpenHAB developers and “know” the guy who builds these images. I trust him and do run this image on my home network. I also know where to go to view his Dockerfiles, and I know how his images are built.

But there are images posted by random Internet denizens – I run Docker on my personal Windows laptop and needed to access the underlying MobyVM. The image justincormack/nsenter1 will do it … but I have no idea who this person is. A quick search of the Internet yielded a Dockerfile for this image, but there’s nothing that ensures the image on Docker Hub is actually built with this file. It’s safer to use the Dockerfile to build your own version of the image.

GitLab – Using the Docker Executor for Testing

Setting up gitlab-runner to use a Docker executor: You need Docker running on the gitlab-runner host. In my sandbox, I have GitLab running as a Docker container. Instead of installing Docker in Docker, I have mounted the host Docker socket to the GitLab container. You’ll need to add the –privileged flag, and since I’m using Windows … my mount path is funky. But it works.

docker run –detach –hostname –publish 443:443 –publish 80:80 –publish 22:22 –name gitlab -v //var/run/docker.sock:/var/run/docker.sock –privileged gitlab/gitlab-ee:latest

Register the runner using “docker-runner register”. I always specify the image in my CI YAML file, so the default image is immaterial … but I’ve encountered groups with an image that mirrors the production servers who set that image as the default.

Edit /etc/gitlab-runner/config.toml and change “privileged = false” to true.

Start the runner (docker-runner start). In the GitLab Admin Area, navigate to Overview => Runners and select the one we just created. When a project is updated, tags can be used to select an appropriate runner. Because most of my testing is done with the shell executor, the runner which uses the shell executor has no tags and the runner which uses the Docker executor is tagged with “runner-docker”. You can require all jobs include a tag to select the appropriate runner (which avoids someone accidentally forgetting a tag and having their project processed through the wrong runner).

An image – you’ll need an image. You can use base images from the Docker Hub registry or create your own image. You can add components in the before_script or use a Dockerfile to build an image from the parent image.

Now we’re ready to use the Docker executor! Create your CI YAML file.

If you are not using the default image, start with “image: <the image you want>”.

We don’t want phpunit in the running image, but I use it for testing. Thus, I need a before_script component to install the phpunit package.

If you’ve used a tag to restrict what is run in your Docker-executor based runner, add the appropriate tag. Include the tester command line.


- test

# Install dependencies
- bash ci/

stage: test
- runner-docker
- phpunit --configuration phpunit_myapp.xml

yum install php-phpunit-PHPUnit

Now when you commit changes to the repository, the Docker-executor based runner will be used for the CI/CD pipeline. A transient Docker container will be created with the image, your before_script will be executed, and then the test script will be run within the container.


Building a Docker Image From a Parent Image

Docker maintains a registry of pre-built images that may be all you need. Put some thought into it, though – don’t just trust any image you find on the registry. When a pre-built image doesn’t meet your needs, you can make your own image based on any base image.

I create a new folder to hold the build instructions, additional configuration files, and notes about the build process. In that folder, create a file named “Dockerfile” which controls the image build.

You’ll need to specify the base image for your build using the FROM directive. I have examples here for installing sqlite3 on both a CentOS and Ubuntu base image. Add LABELs indicating the purpose of the image and who maintains it. Use the “RUN” directives to provide instructions to modify the base image to your needs – install what you want, create folders or files, clean up anything you don’t want.

The ENTRYPOINT is what runs when the container starts – it can be an executable, as in this case, or a script file.

If you need to expose ports for inter-container communication, add a list of ports to expose. SQLite is self-contained, but a microservice environment may have “EXPOSE 21443” to allow other containers to communicate with it on port 21443. Note this is different than binding the container port to a host – my Apache web server container has 443 bound; but that’s done in the “docker run” line where the container is built, not in the Dockerfile for the image.

Example – SQLite3 on CentOS

FROM centos:latest

LABEL "version": "latest"
LABEL "description": "CentOS server running SQLite3"
LABEL "maintainer": ""

RUN yum -y install glibc.i686
RUN yum -y install zlib.i686
RUN yum -y install wget
RUN yum -y install unzip

RUN cd /root && wget && unzip -j -d /usr/bin -o /root/ && rm -f /root/

RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN mkdir -p /db


ENTRYPOINT [ "sqlite3" ]

Example – SQLite3 on Ubuntu

FROM ubuntu:latest

LABEL "version": "latest"
LABEL "description": "Ubuntu server running SQLite3"
LABEL "maintainer": ""

RUN DEBIAN_FRONTEND=noninteractive apt-get -yq update

RUN DEBIAN_FRONTEND=noninteractive apt-get -yq install sqlite3

RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN mkdir -p /db


ENTRYPOINT [ "sqlite3" ]

Save your Dockerfile and build your container using “docker build -t image/name .”

Use “docker images” to confirm the image has been successfully created.

And start a container using your image:

docker run -it --name ljrimagetest ljr/sqllite3

If you are having problems starting your container, change the entrypoint to something like “/bin/bash” – this will drop you to the host’s command line instead of the application. From there, you can troubleshoot your launch problems and sort the problem. The CentOS sqlite install, as an example, required glib and zlib components to run. Rather than trying something, rebuilding the image, launching a container, and looping until sqlite3 launched … I used bash as my entrypoint, installed packages until the application ran, and then modified the Dockerfile and rebuilt the image.

At this point, you can tag the image and upload it to a registry. I use my GitLab server to store Docker images. There is a DockerImages repository

To tag the image, use

docker tag ljr/sqllite3

And upload the image to your registry (you may need to authenticate first)

docker push

If you are using GitLab as your registry, navigate to the repository. Select Packages => Container Registry

Here’s my Sqlite3 image


GitLab – Using the built-in Docker Registry

GitLab has a built-in Docker registry that you can use for projects. With the Omnibus install (or a container based on the official Docker image), enabling the registry is as simple as adding a config line to your gitlab.rb (this assumes you have a SSL key at /etc/gitlab/ssl named with the fully qualified hostname and using .crt for the public key and .key for the private key

registry_external_url ‘’

Then just tag an image to a project’s repository URL

docker tag ossautomation/cent68php56

Log in and push the image:

D:\git\ljtestproject-dockerexecutor>docker login
Username: lisa
Login Succeeded

D:\git\ljtestproject-dockerexecutor>docker push
The push refers to repository []
45c3e2f5d139: Pushing [=> ] 33.31MB/1.619GB

Accessing MobyVM (And adding an exposed port to an existing Docker container)

I needed to map an addition port into an existing Docker container. Now I know the right thing to do is to create a new container and do it right this time but GitLab’s container has problems running on the Windows Docker Desktop. Permission-based problems that I’m not particularly included to attempt to sort out just to run a simple sandbox. Which means I’d need to drop my config file back in place & recreate my sandbox projects. And since I’m using CI/CD variables which don’t export … recreating the sandbox projects is a bit of a PITA.

On Linux, I can fix this by editing the config.v2.json and hostconfig.json files … but this is Windows running a funky Hyper-V Linux. And it turns out you can access the files on this MobyVM.

docker run -it --rm --privileged --pid=host justincormack/nsenter1

Now I’m able to cd into /var/lib/docker/containers, find the full ID for my GitLab container and cd into it, and edit the two config files. If it is running, you need to stop the container prior to editing the config files.

config.v2.json — add the port to “ExposedPorts”


hostconfig.json — add the port to “PortBindings”



Stop the Windows Docker service, start it, then start the container again. Voila! The new port for the container registry is there without recreating the container.