Skip to content

Latest commit

 

History

History
850 lines (563 loc) · 13.1 KB

deck.mdx

File metadata and controls

850 lines (563 loc) · 13.1 KB

import { Appear, Notes, Head, } from 'mdx-deck' import { Invert } from 'mdx-deck/layouts' // import future from '@mdx-deck/themes/future' import highlight from '@mdx-deck/themes/syntax-highlighter-prism'

export const themes = [ // future, highlight ]

<title>Docker Field Guide Workshop</title>

Moby Dock

Docker Workshop:

A Field Guide

Tom Gallacher, Ph.D
view on github

Goal

Provide context on the essentials of Docker to get started and be productive


Prerequisites

  • Docker installed
  • Yarn installed
  • Basic terminal skills
  • Familiarity with NodeJS (exercises)

Agenda

  • Docker fundamentals
  • Coffee
  • Dockerfile
  • Lunch
  • Multi-stage builds
  • Coffee
  • Dockerfile best practices

Docker Fundamentals


import { Split } from 'mdx-deck/layouts'

Why Docker?

  • Lightweight
  • Portable
  • Scalable
(1) containers run natively on Linux, sharing kernal; lower overhead (1) VMs full guest OS with virutal access to host resources through "hypervisor". (1) Flexible: Even the most complex applications can be containerized. (1) Lightweight: Containers leverage and share the host kernel. (1) Interchangeable: You can deploy updates and upgrades on-the-fly. (1) Portable: You can build locally, deploy to the cloud, and run anywhere. (1) Scalable: You can increase and automatically distribute container replicas. (1) Stackable: You can stack services vertically and on-the-fly.

Terminology, terminology, terminology

  • docker vs. docker-engine vs. docker-machine vs. docker-compose
  • image vs. container vs. swarms vs. stacks vs. services
  • Dockerfile vs docker-compose.yml

First: Docker Architecture


Client - Server


docker-compose client CLI for managing multi-docker container applications
docker-machine remote management of distributed "docker-daemon"s
docker docker "client" CLI
docker api interface between "docker-daemon" and "docker" client
docker daemon the long runing "docker-engine" system process
docker-engine brains which enables Docker's container runtime technology

Docker CLI

(Time to get involved)

Getting started


Is Docker installed?

docker --version

=> Docker version 18.09.2, build 6247962


Hello world


Hello world

docker run hello-world

Example Output


Docker (CLI) Concepts

  • Stack
  • Swarm
  • Service
  • Container
  • Image
  • Dockerfile
  • (Volumes)

Stack inter-related "services" running in concert
Swarm collection of multiple "Docker daemons" joined within a single "cluster"
Service configuration of a container which can also be scaled across multiple "Docker daemons"
Container running instance of an "image"
Image read-only snapshot for being able to run a process
Dockerfile recipe guide for creating an "image"
Volumes persistent storage

Docker (CLI) Concepts

We'll focus on:

  • Stack
  • Swarm
  • Service
  • Container
  • Image
  • Dockerfile
  • (Volumes)
- Let's jumble the order a bit to make it easier.

Our first app

"Talking whales"

  1. Pull images from public registry
  2. Run images as containers and execute their commands
  3. Understand how to clean up

Pull the required image from public registry

docker pull docker/whalesay

Check the image is available on your machine

docker images

Run the image to get a container

(and provide the args to the default CMD)

docker run docker/whalesay 'docker rules!'

Is the container still running?

How do we check?

How do we re-execute the command?


docker container ls

View all containers (including those which are stopped)

docker container ls -a

Tip

Using docker run will auto pull images which it can't find locally


Cleanup

  1. Remove containers
  2. Remove images

Cleanup Containers

Get container ID

docker container ls

Remove

docker container rm <container_id>

Tip

Delete all (stopped) containers

docker container prune

See: https://docs.docker.com/engine/reference/commandline/container_prune/


Cleanup Images

Get image ID

docker image ls

Remove

docker image rm <image_id>

Tip

Delete all unused images

docker image prune

See: https://docs.docker.com/engine/reference/commandline/image_prune/


Tip

  • Delete all containers first before deleting images
  • Containers are tied to an image so you get an error if you delete an image that has a linked container reference

Recap

  1. We've played around with some Docker resource concepts:
    • image, container
  2. We've seem some of the Docker CLI commands:
    • pull, run, container, image
  3. And we've done all this with some pre-built images from the docker hub

How do we create our own images?


Dockerfile

Recipe guide for creating Docker images

https://docs.docker.com/engine/reference/builder/

- Build context

Dockerfile: FROM

Configure the 'base image'

FROM <image>[:<tag>]

*required


FROM: Examples

FROM node:lts-jessie
FROM node:10-alpine
FROM node:12-jessie

FROM ruby:2.6-jessie
FROM ruby:2.5-stretch
FROM ruby:2.4-stretch

Dockerfile: RUN

Execute command and 'commit' result on top of current 'layer'

RUN <command>

RUN: Examples

RUN yarn install
RUN bundle install

# Note this is bad;
# we'll find out later why
RUN yum update
RUN yum install -y wget

Dockerfile: COPY

Copy files/dirs from src (on host) to dest (in image).

COPY [--chown=<user>:<group>] <src>... <dest>

COPY: Examples

COPY ./deploy.sh /deploy.sh

# Some globs can be used as well
COPY package*.json /home/app/

COPY /src /home/app/

Dockerfile: EXPOSE

'Expose' container port to host

EXPOSE <port>[/<protocol>]

EXPOSE: Examples

EXPOSE 80
EXPOSE 443

EXPOSE 5432
EXPOSE 1234/udp

TCP is default protocol


Dockerfile: VOLUME

Creates a mount point with the specified name and declare it as holding external (host) data.

VOLUME ["<path>"]
VOLUME <path>

VOLUME: Examples

VOLUME /myvol
VOLUME ["/var/www/html"]
VOLUME ["/data"]

Dockerfile: USER

Configures user for: RUN, CMD or ENTRYPOINT directives

USER <user>[:<group>] or
USER <UID>[:<GID>]

USER: Examples

USER root
USER node
USER www-data
# use user id and group id
USER 1001:1001

Dockerfile: CMD

Set default process to execute on startup

# preferred
CMD ["executable","param1","param2"]
# pass params to ENTRYPOINT
CMD ["param1","param2"]
# Shell form (executed in /bin/sh -c)
CMD command param1 param2

CMD: Examples

CMD ["node", "app.js"]
CMD ./run.sh

Dockerfile: ENTRYPOINT

Configure container that runs as an executable

# exec form
ENTRYPOINT ["executable", "param1", "param2"]
# shell form
ENTRYPOINT command param1 param2

ENTRYPOINT: Examples

ENTRYPOINT ["top", "-b"]

ENTRYPOINT ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

Tip

Combine CMD with ENTRYPOINT to provide configurable defaults

# Dockerfile -> imaeg name = 'custom-tail'
ENTRYPOINT ["tail"]
CMD ["-5"]

# tails 5 lines
docker run custom-tail
# tails 15 lines
docker run custom-tail -15

Exercise 01

NodeJS App: Creating a Dockerfile


Tasks

Folder: 01-nodejs-dockerfile

  1. Complete the todo list in the template file.
  2. Build image
  3. Run container and check output

(clone repo if you haven't already)


Solution


FROM node:lts-jessie
# Create app directory
WORKDIR /home/node/app
# Install app dependencies
COPY package.json .
# Add app
COPY . .

RUN yarn install
EXPOSE 8080
CMD ["node", "app.js" ]

cd exercies/01-nodejs-dockerfile
# build image
docker build -t my-node-app .
# run container
docker run -p 8080:8080 my-node-app
# check output
curl -i localhost:8080

Docker: DevExp

  • We have an app all packaged in a Docker image

  • What if we want to do rapid iterations?

  • Do we need to keep rebuilding the image over and over?


Exercise 02: Bind mounts

Use a bind-mount to create an effective developer experience.


Tasks

Folder: 02-nodejs-bind-mount

  1. Run yarn install in the exercise folder
  2. Build the image
  3. Run the container using docker run
  4. Customise the run command to bind mount the exercise folder into the container.
  5. Change the contents of app.js response and verify it changes in the output

Solution

cd exercies/02-nodejs-bind-mount
# build image
docker build -t my-node-dev-app .
# run container
docker run \
  -p 8080:8080 \
  -v $PWD:/home/node/app \
  my-node-dev-app

Solution: contd.

# check output
curl -i localhost:8080
# Change `app.js` response
curl -i localhost:8080

Multi-stage builds

Docker image optimisation


Directives

We need to modify 2 of our directives:

  • FROM <image>[:<tag>] AS <stage_name>
  • COPY --from=<stage_ref | base>

Exercise 03

Folder: 03-nodejs-multistage-build

  1. Optimise our Dockerfile using the multi-stage builds pattern.

Solution


FROM node:lts-jessie AS build
WORKDIR /home/node/app
COPY package*.json ./
RUN yarn install
COPY . .

# ...contd

# ...

RUN yarn build
FROM node:lts-jessie
WORKDIR /home/node/app
COPY --from=build /home/node/app/dist .

EXPOSE 8080
CMD ["node", "app.js" ]

Docker

Best practices


  1. Single process per container
  2. Optimise for small image sizes
  3. Combine shared components into a base image
  4. Don't rely on :latest tag
  5. Use volumes for storage
- base image enables caching and better memory usage - Use `bind mounts` in development

Dockerfile

Best practices


  1. Order directives from least to most likely to change,
    • e.g. COPY should come later
  2. Increase specificity in COPY to avoid un-intentional cache bust,
    • e.g. don't use COPY . <dest>
  3. Combine appropriate RUN directives
  4. Use official images where possible
    • reduces maintenance time, reduce size, configured for container use
  5. Use multi-stage builds where possible
  6. Enable 'Buildkit' for improved build times
    • export DOCKER_BUILDKIT=1

Optimisations

Simple Examples


Improve caching


FROM node:lts-jessie
WORKDIR /home/node/app
COPY package.json yarn.lock .
COPY . .

RUN yarn install

EXPOSE 8080
CMD ["node", "app.js" ]

FROM node:lts-jessie
WORKDIR /home/node/app
COPY package.json yarn.lock .

RUN yarn install

COPY ./*.js .

EXPOSE 8080
CMD ["node", "app.js" ]

Compress RUN statements


FROM ubuntu:latest

RUN apt-get update
RUN apt-get install -y nginx

RUN groupadd -r nginx
RUN useradd --no-log-init \
  -r -d /var/www/app -g nginx nginx

...

# Ignore this optimisation anti-pattern
FROM ubuntu:latest

RUN apt-get update \
  && apt-get install -y nginx --no-install-recommends \
  && rm -rf /var/lib/apt/lists/*

RUN groupadd -r nginx \
  && useradd --no-log-init \
  -r -d /var/www/app -g nginx nginx

# Now we're good
FROM nginx:latest

...

/END