Slide 1

Slide 1 text

Optimizing Docker for IoT with Multi-Stage Builds @pimterry

Slide 2

Slide 2 text

@pimterry

Slide 3

Slide 3 text

@pimterry

Slide 4

Slide 4 text

IoT is still new, and still comes with hard problems @pimterry

Slide 5

Slide 5 text

How do you deploy, monitor and manage IoT applications? @pimterry

Slide 6

Slide 6 text

@pimterry

Slide 7

Slide 7 text

Isolation Reproducibility Application Delivery @pimterry Why Docker + IoT?

Slide 8

Slide 8 text

@pimterry

Slide 9

Slide 9 text

IoT is still new, and still comes with lots of hard problems @pimterry

Slide 10

Slide 10 text

How do you secure code that’s literally in your users’ hands? @pimterry

Slide 11

Slide 11 text

How do you quickly deliver application changes in an inherently unreliable environment? @pimterry

Slide 12

Slide 12 text

@pimterry

Slide 13

Slide 13 text

Lightweight OS for IoT with Docker Optimised support for 25+ boards Preconfigured flexible networking support Secure by default throughout Read-only root partition + AUFS data partition @pimterry

Slide 14

Slide 14 text

@pimterry

Slide 15

Slide 15 text

@pimterry Moby-based container engine for IoT Fully Docker-compatible 3.5x smaller than Docker 10-70x more bandwidth efficient ‘docker pull’ Built for low-memory environments Atomic & durable pulls

Slide 16

Slide 16 text

Resin.io device stack @pimterry

Slide 17

Slide 17 text

There’s only so much you can do at the device level @pimterry

Slide 18

Slide 18 text

How can I build smaller images? @pimterry

Slide 19

Slide 19 text

How can I hide build secrets? @pimterry

Slide 20

Slide 20 text

How can I keep things out of my image? @pimterry

Slide 21

Slide 21 text

FROM node:8 ARG NPM_TOKEN RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \ npm whoami RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY . . RUN npm install && npm run build && rm ~/.npmrc CMD [ "/usr/local/bin/node", "./built.js" ] @pimterry The Problem

Slide 22

Slide 22 text

FROM node:8 ARG NPM_TOKEN RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \ npm whoami RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY . . RUN npm install && npm run build && rm ~/.npmrc CMD [ "/usr/local/bin/node", "./built.js" ] @pimterry The Problem 700MB image (for an empty project)

Slide 23

Slide 23 text

FROM node:8 ARG NPM_TOKEN RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \ npm whoami RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY . . RUN npm install && npm run build && rm ~/.npmrc CMD [ "/usr/local/bin/node", "./built.js" ] @pimterry The Problem

Slide 24

Slide 24 text

➜ docker image inspect 274627c8a3a2 [ { "Id": "sha256:274627c8a3a28d...", ... "GraphDriver": { "Data": { "LowerDir": "...", "MergedDir": "/var/lib/docker/overlay2/61038f.../merged", "UpperDir": "/var/lib/docker/overlay2/61038f.../diff", "WorkDir": "/var/lib/docker/overlay2/61038f.../work" }, }, } ] @pimterry The Problem

Slide 25

Slide 25 text

➜ sudo cat /var/lib/docker/overlay2/61038f.../diff/root/.npmrc //registry.npmjs.org/:_authToken= @pimterry The Problem

Slide 26

Slide 26 text

➜ docker history e5936788a37a IMAGE CREATED CREATED BY SIZE e5936788a37a 2 minutes ago /bin/sh -c #(nop) CMD ["/usr/local/bin/no... 0B 79fad4492e73 2 minutes ago |1 NPM_TOKEN= ... 537kB 373ea011abf1 2 minutes ago /bin/sh -c #(nop) COPY dir:6ffc7f0ac3036c2... 459B 155133064e6a 2 minutes ago /bin/sh -c #(nop) WORKDIR /usr/src/app 0B 2a5d69935132 2 minutes ago |1 NPM_TOKEN= ... 0B 274627c8a3a2 2 minutes ago |1 NPM_TOKEN= ... 125B 8f14bc306cc4 3 minutes ago /bin/sh -c #(nop) ARG NPM_TOKEN 0B 2eeae8debf3d 2 days ago /bin/sh -c #(nop) CMD ["node"] 0B 2 days ago /bin/sh -c set -ex && for key in 6A0... 4.17MB 2 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.3.2 0B 2 days ago /bin/sh -c ARCH= && dpkgArch="$(dpkg --pri... 56.6MB 2 days ago /bin/sh -c #(nop) ENV NODE_VERSION=8.9.3 0B 2 days ago /bin/sh -c set -ex && for key in 94A... 129kB ... @pimterry The Problem

Slide 27

Slide 27 text

Combine RUN commands @pimterry

Slide 28

Slide 28 text

@pimterry Combined RUN Limitations Breaks caching Doesn’t help with non-RUN commands Doesn’t erase history

Slide 29

Slide 29 text

docker build --squash @pimterry

Slide 30

Slide 30 text

@pimterry --squash Limitations Requires ‘experimental’ flag Silent disaster if you forget to squash Can be less efficient Doesn’t erase history

Slide 31

Slide 31 text

Rename previous Dockerfile to Dockerfile.build docker build . -f Dockerfile.build Copy build output from image to local file with docker cp Build production Dockerfile ⟶ @pimterry Builder Pattern Dockerfile: FROM node:8-alpine RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY ./built.js . CMD [ "/usr/local/bin/node", "./built.js" ]

Slide 32

Slide 32 text

@pimterry Builder Pattern Limitations Pretty complicated Needs manual steps/wrapper automation Encourages different dev/prod images & workflow

Slide 33

Slide 33 text

Multi-stage Builds @pimterry

Slide 34

Slide 34 text

@pimterry Build in stages Pull content between stages Multi-stage Builds:

Slide 35

Slide 35 text

FROM base repeatedly @pimterry

Slide 36

Slide 36 text

FROM node:latest ... FROM node:alpine ... @pimterry

Slide 37

Slide 37 text

FROM base as first-stage ... FROM base2 ... FROM first-stage ... @pimterry

Slide 38

Slide 38 text

FROM base as first-stage ... FROM base2 COPY --from=first-stage /a /b @pimterry

Slide 39

Slide 39 text

Bonus: COPY --from=any-image @pimterry

Slide 40

Slide 40 text

History & layers for each stage are independent @pimterry

Slide 41

Slide 41 text

The last stage is the end result @pimterry

Slide 42

Slide 42 text

(Unless it’s not) docker build --target= @pimterry

Slide 43

Slide 43 text

FROM base as build-stage ... FROM build-stage as test-stage ... FROM build-stage @pimterry Change default target:

Slide 44

Slide 44 text

Watch out: Non-target stages are considered dangling @pimterry

Slide 45

Slide 45 text

Watch out: --cache-from breaks multi-stage caching @pimterry

Slide 46

Slide 46 text

FROM node:8 AS build ARG NPM_TOKEN RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \ npm whoami RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY . . RUN npm install && npm run build CMD [ "/usr/local/bin/node", "./built.js" ] FROM node:8-alpine RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY --from=build /usr/src/app/built.js . CMD [ "/usr/local/bin/node", "./built.js" ] @pimterry Fixed Multi-Stage Dockerfile

Slide 47

Slide 47 text

FROM node:8 AS build ARG NPM_TOKEN RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \ npm whoami RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY . . RUN npm install && npm run build CMD [ "/usr/local/bin/node", "./built.js" ] FROM node:8-alpine RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY --from=build /usr/src/app/built.js . CMD [ "/usr/local/bin/node", "./built.js" ] @pimterry Fixed Multi-Stage Dockerfile 70MB image (90% smaller)

Slide 48

Slide 48 text

IMAGE CREATED CREATED BY SIZE 0e1999330a57 5 seconds ago /bin/sh -c #(nop) CMD ["/usr/local/bin/no... 0B bdd51c6251d5 6 seconds ago /bin/sh -c #(nop) COPY file:19fa00c24fa256... 614kB fadd597467a6 6 seconds ago /bin/sh -c #(nop) WORKDIR /usr/src/app 0B e7bffff20b93 6 seconds ago /bin/sh -c mkdir -p /usr/src/app 0B 144aaf4b1367 5 days ago /bin/sh -c #(nop) CMD ["node"] 0B 5 days ago /bin/sh -c apk add --no-cache --virtual .b... 4.19MB 5 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.3.2 0B 5 days ago /bin/sh -c addgroup -g 1000 node && ad... 59.6MB 5 days ago /bin/sh -c #(nop) ENV NODE_VERSION=8.9.3 0B 12 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B 12 days ago /bin/sh -c #(nop) ADD file:cb381165dec3689... 3.97MB @pimterry Hide secure credentials: Secrets don’t appear in the history Previous stage layers are separate

Slide 49

Slide 49 text

@pimterry Take huge images: FROM ekidd/rust-musl-builder ADD . ./ RUN sudo chown -R rust:rust /home/rust RUN cargo build --release EXPOSE 8000 CMD ["/home/rust/src/.../rust-server"] Resulting image: 2.3 GB github.com/pimterry/multistage-demos/tree/master/rust-server

Slide 50

Slide 50 text

@pimterry Build tiny images: FROM ekidd/rust-musl-builder as build ADD . ./ RUN sudo chown -R rust:rust /home/rust RUN cargo build --release FROM scratch COPY --from=build /home/rust/src/.../rust-server / EXPOSE 8000 CMD ["/rust-server"] Resulting image: 4.8 MB github.com/pimterry/multistage-demos/tree/master/rust-server

Slide 51

Slide 51 text

Supported in Docker >= 17.05 @pimterry

Slide 52

Slide 52 text

Multi-stage builds let you: Control image history Control image size Declaratively describe your build @pimterry

Slide 53

Slide 53 text

Optimizing Docker for IoT with Multi-Stage Builds @pimterry