Containerization
In this post we will use docker and GitHub to containerize an ASP.NET Hello World app
What is a container?
A container is an application packaged with its own runtime environment. Like a shipping container it is standardized and modular to scale and be reused and shipped easily. Compared to virtual machines a container doesn’t virtualize hardware and allocates memory, a container is therefor more flexible and more lightweight. Instead of an hypervisor the container runs by a container engine/runtime and is built from an container-image which in turn is built from an manifest file (in our case the Dockerfile).
Note that even if the containers have an isolated environment you can open and map ports (in the same sense as you open ports with any OS) for communicating with the outside environment. As a side note you can even bash into an containers command-line from the host machine.
There are a few different container technologies but for this assignment Docker is used.
System:
- MacOS Big Sur Version 11.5.2
- Visual Studio Code
- Docker Extension
- macOS shell
- Docker Desktop Version 4.0.0
- Docker CLI
Docker installation
I tried a in a few ways to install docker CLI with Homebrew (macOS package manager), but in the end it worked when I installed it through the browser, Docker Desktop ships with Docker CLI.
Setup Test-project ASP.NET
See dotnet CLI commands in last blog
Generate Docker files
Generated docker files automatically with Visual Studio Code Docker Extension
├── Dockerfile
├── docker-compose.debug.yml
├── docker-compose.yml
Dockerfile
The dockerfile is the blueprint of the image and it always starts from another image.
Commands:
FROM - points to another docker image to build upon.
COPY - is a command that copies something from the build environment (host) to inside the container.
WORKDIR - takes you to that directory, similar to shell “cd /src”
ENTRYPOINT - Command line arguments that describes where to enter the container, what process to run.
Working with Docker Commands
You can either build the docker image from the “Dockerfile” with configuration done with the CLI parameters or build the image pre-configured with the docker-compose file. When I worked with the Dockerfile I used following commands:
# docker build -t image-name:tag -f <Dockerfile-path> <url>
# builds image of the Dockerfile blueprint
docker build -t ASP-IMG:v0.1 -f . .
docker images
# use to get image id|name
# docker container run -p <host-port>:<container-port> --name <new-container-name> <image>
# Creates and starts a container with port mapped to host port.
docker container run -p 8080:5000 --name asp-container ASP-IMG
docker ps
# display running containers
# To check the web-app is running
curl http://localhost:8080/
#$ Hello World!%
Configuring with docker-compose
Docker compose is a configuration file that simplify the creation of an image from a Dockerfile. Instead of long commands like the “docker container run -p…” command above you can edit the docker-compose file and simply run:
docker-compose up
This helps the automation of the image build but also shows intent of the developer. Following yaml is from the repos docker-compose.yml
version: "3.4"
services:
asphelloworlddocker:
image: asphelloworlddocker
build:
context: . # this states the directory of compose-file
dockerfile: ./Dockerfile # path to Dockerfile
ports:
- 8080:5000
GitHub Pipeline
In this project I have based the workflow on the docker-publish.yml which is one of the GitHub starter-workflows. Its good to note that there are a lot of workflows to use for different scenarios and it is probably most efficient to start off with a given workflow/action. If you’re looking for a workflow, search on the GitHub Marketplace where you can find all kinds.
name: Docker-Modified
# Simplified the event trigger to on push
on:
push
env:
REGISTRY: ghcr.io # A container registry by github
IMAGE_NAME: ${ { github.repository } } # Repository name variable
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Login against a Docker registry
# https://github.com/docker/login-action # the repo for the action
- name: Log into registry ${ { env.REGISTRY } }
if: github.event_name != 'pull_request'
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
with:
registry: ${ { env.REGISTRY } }
username: ${ { github.actor } }
password: ${ { secrets.ASP_NET_DOCKER } } # see secrets below
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${ { env.REGISTRY } }/${ { env.IMAGE_NAME } }
# https://github.com/docker/build-push-action
- name: Build and push Docker image
# uses keyword points to another workflow inside a github repository
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
# inparameters for the external workflow (defined in the repo action.yml file)
context: .
push: ${ { github.event_name != 'pull_request' } }
tags: ${ { steps.meta.outputs.tags } }
labels: ${ { steps.meta.outputs.labels } }
Secrets
In the yaml above we have a few variables that hides secrets and tokens. For example:
password: ${ { secrets.ASP_NET_DOCKER } }
This secret variable is stored inside the repository on GitHub and can be added through Repository -> Settings -> Secrets -> New Repository secret.
But we also need to generate them ourselves through our personal GitHub profile -> settings -> developer settings -> personal access token -> New personal access token.
For publishing a container to the GitHub Registry we need to create a token that has write:packages Scope.
Handling Tokens
To protect the secrets from entering logs and even being visual on screen I added the token inside my macOS keychain that needs an elevated password to retrieve them. I retrieve them to clipboard and pipe them as standard input to the command like below.
pbpaste | docker login ghcr.io --username <github-user-name> --password-stdin
# macOS command pbpaste pipes what contained in clipboard to std.in to next command
References
WTF is a container? Article, 4 min
Containerization Explained video, 8 min