Skip to main content

Docker & Kubernetes 101


Docker is a tool designed to make it easier to create, deploy, and run applications by using containers. Containers allow a developer to package up an application with all of the parts it needs, such as libraries and other dependencies, and ship it all out as one package. —, What is Docker?


Don't worry about the system your application will ultimately run on.

Install Docker

Install at Docker's getting-started

Setup Docker

First, ensure you are using an up-to-date docker engine. In the terminal,

  1. Run docker info and check for any errors
  2. Run docker --version and ensure you have version >=17.06

Setup Docker Resources

Ensure you have set enough resources for Docker to run smoothly: In the taskbar, click the whale icon → Preferences → Advanced

(I set 6 CPUs and 8GB RAM for Docker)


docker settings

World Smallest Image

Every single Docker container begins as a pure vanilla Linux machine that knows nothing.

know nothing

Where It All Begins

A Docker image consists of read-only layers each of which represents a Dockerfile instruction. The layers are stacked and each one is a delta of the changes from the previous layer.

Images start as a fresh, isolated machine and the very same dependencies get added to it. Every. Single. Time.

FROM scratch

ADD hello /

CMD ["/hello"]

Build your First Image

docker build -t tiny-hello-world .
docker images

Run your First Container

docker run tiny-hello-world
docker ps -a

Clean After Yourself

Delete a container:

docker rm condescending_ptolemy

Next time, run container & delete after exit:

docker run --rm tiny-hello-world


Delete all stopped containers:

docker container prune

Delete all dangling images:

docker image prune

Bob and Dylan

My First Containerized Node Application

I recommend Dockerfile documentation. It's very well written.


Node service:

# creates a layer from the node:carbon Docker image
FROM node:8.14-alpine

# create the app directory for inside the Docker image
WORKDIR /usr/src/app

# copy and install app dependencies from the package.json (and the package-lock.json) into the root of the directory created above
COPY package*.json ./
RUN yarn install

# bundle app source inside Docker image
COPY . .

# expose port 8080 to have it mapped by Docker daemon

# define the command to run the app
CMD [ "yarn", "run", "start" ]


var express = require('express');
var app = express();

process.on('SIGINT', function () {

app.get('/', function (req, res) {
res.send("Hello World!

app.listen(3000, function () {
console.log('Example app listening on port 3000!');


"name": "docker-101-scene-02",
"version": "1.0.0",
"main": "index.js",
"author": "Rafael Bodill",
"license": "MIT",
"scripts": {
"start": "node app.js"
"dependencies": {
"express": "^4.16.4"

Let's Build & Run

docker build -t test-node .
docker run -it --rm test-node

On a new terminal:

curl localhost:3000

Where's my service? :(

Expose Ports to Host

docker run -it --rm -p 3000:3000 test-node

On a new terminal:

curl localhost:3000


Let's Add a Route

Append to app.js:

app.get('/healthz', function (req, res) { res.send('OK') })

Rebuild image:

docker build -t test-node .
docker run -it --rm test-node

Docker Layer Caching

Docker Caching

Docker Layers

$ docker history test-node

d17562ebd02e 27 minutes ago 0B CMD ["yarn" "start"]
c24c2a8c7a2a 27 minutes ago 0B EXPOSE 3000
001439668f54 27 minutes ago 1.78MB COPY dir:0dc6500de44402cb2…
24e151c2462b 27 minutes ago 7.13MB yarn install
9dd639668f68 27 minutes ago 226B COPY file:b365e33dfa656564…
a93bcafb8a76 27 minutes ago 0B WORKDIR /usr/src/app

.. Wait .. Our app.js is 1.78MB?

Ignoring Files

Create a .dockerignore file with:


Rebuild image:

docker build -t test-node .
docker history test-node

Now it's better.

Bob poster child

Linux Containers

Images vs. Containers

Docker images are executable packages that include everything needed to run an application — the code, a runtime, libraries, environment variables, etc.

Docker containers are a runtime instance of an image — what the image becomes in memory when executed.

Examples of Docker containers. Each one comes from a specific Docker image.

Docker containers

Entering Container's Shell

docker run -d --name bob -p 3000:3000 test-node
docker exec -it bob sh

You are now INSIDE the matrix container!

> netstat -ntulp
> wget -q -O- localhost:3000
> top
> free -m
> exit

Running Different Commands

docker run --rm -it test-node sh
docker run --rm -it test-node ls -alp

View Container Information

docker inspect bob | less
docker logs -f bob
docker stats bob

Manage State

docker stop bob
docker start bob
docker restart bob
docker rm -f bob

Dylan small man

Private Services

docker run --rm -p 3000:3000 artifactory.local/myapp

Oh oh. What's missing here?

Mount Volumes

Let's mount our configuration from a different Git repository:

git clone git@git.local:mycompany/myconfig.git

docker run --rm -v $PWD/myconfig:/etc/myapp \
-p 3000:3000 artifactory.local/myapp

Dog-gone it. What else are we missing?

Environment Variables

Oh. Our environment variable! It tells the configuration where to read from:

docker run --name bob -v $PWD/myconfig:/etc/myapp \
-e MYAPP_CFG_ROOT=/etc/myapp -p 3000:3000 \

Viewing Logs

In a different terminal,

docker logs -f bob
docker exec -it bob tail -f /var/log/myapp/api.log

Bob the beach

Simplifying Workspaces

Docker Compose offers a number of benefits over the Docker CLI in all stages of development.

Essentially, what Docker Compose is, is a recipe card — it's a recipe of services that make up an application and the docker-compose.yml dictates how the services are mixed together.

Simple Example

Let's inspect our docker-compose.yml file:

version: "2"

image: artifactory.local/myapp
MYAPP_CFG_ROOT: /etc/myapp
- "3000:3000"
- ./myconfig:/etc/myapp

Running Your Composition

git clone git@git.local:mycompany/myconfig.git
docker-compose up

On a different terminal:

curl localhost:3000

Let's Add (3rd)Parties!

version: "2"

image: artifactory.local/myapp
MYAPP_CFG_ROOT: /etc/myapp
- "3000:3000"
- ./myconfig:/etc/myapp

image: "redis:alpine"

Let's run it:

docker-compose up


Give me morrrre!

version: "2"

image: artifactory.local/myapp
MYAPP_CFG_ROOT: /etc/myapp
- "3000:3000"
- ./myconfig:/etc/myapp

image: "redis:alpine"

image: "mysql:5.7"

Let's run it:

docker-compose up

Access Services

# Access MySQL:
docker exec -it scene-05_mysql_1 mysql -u root -p

# Access Redis:
docker exec -it scene-05_redis_1 redis-cli GET /

More on Docker-Compose

  • docker-compose ps - Lists all services in network.
  • docker-compose up - Brings up the network & services.
  • docker-compose stop - Stops the network, saves state of services.
  • docker-compose start - Restarts the services.
  • docker-compose down - Burn it all down! Destroy network & services.

Bob and Lucy

Kubernetes (K8S) 101

Workstation Setup

brew uninstall --force bash-completion
brew install kubernetes-cli kubectx fzf stern bash-completion@2

Append to your ~/.bash_profile for awesome shell completion:

if [ -f /usr/local/share/bash-completion/bash_completion ]; then
. /usr/local/share/bash-completion/bash_completion

Setup kubectl Context

Cluster + User = Context

# Integration
kubectl config set-cluster foobar-int-developer --server= --certificate-authority=$HOME/.kube/int/ca.pem
kubectl config set-credentials int-developer --client-certificate=$HOME/.kube/int/developer.crt --client-key=$HOME/.kube/int/developer.key
kubectl config set-context foobar-int-developer --cluster=foobar-int-developer --user=int-developer

# Staging
kubectl config set-cluster foobar-stg-developer --server= --certificate-authority=$HOME/.kube/stg/ca.pem
kubectl config set-credentials stg-developer --client-certificate=$HOME/.kube/stg/developer.crt --client-key=$HOME/.kube/stg/developer.key
kubectl config set-context foobar-stg-developer --cluster=foobar-stg-developer --user=stg-developer


kubectl config use-context foobar-int-developer
kctx foobar-int-developer
kctx foobar-stg-developer

kubectl Cheat-sheet

kubectl get namespaces
kubectl get deploy
kubectl get -n ingress-nginx deploy
kubectl get -n kube-system deploy
kubectl get --all-namespaces deploy
kubectl get pods -w
kubectl get pods -o wide
kubectl get events

kubectl get pods,svc,deploy
kubectl describe deploy push-service
kubectl top node
kubectl top pods
kubectl logs -f deploy/trips
kubectl logs -f deploy/trips -c trips
kubectl logs --previous trips-7d7f649996-nz876
kubectl exec -it trips-6c6887547d-lhbsb -- tail -f /var/log/myapp/api.log

Examining Deployed Objects

kubectl get deploy
kubectl get pod
kubectl get svc
kubectl get deploy,pod,svc | grep myapp
kubectl get all | grep myapp

Describe Deployment

kubectl describe deploy myapp
kubectl describe pod myapp-649887b547-fh7vw
kubectl top pod | grep myapp

Analyze Logs

kubectl logs -f myapp-649887b547-fh7vw
kubectl exec -it myapp-649887b547-fh7vw -- tail -f /var/log/myapp/api.log

Jump into Container

$ kubectl exec -it myapp-649887b547-fh7vw sh
> top
> free -h
> exit

Mind blown.