Skip to content
:tech
← Handbook

AI-translated. Read the original in Korean

Docker

Docker

This document introduces Docker and provides guidelines to help project teams build images optimized for container orchestration environments.

What Is Docker?

Container technology is closely connected to microservice architecture. A container image typically runs one process in an isolated environment. Running multiple instances of the same container makes it easy to scale the number of processes horizontally. Docker is one of the most widely adopted implementations of container technology; throughout this document, “container” means a Docker container.

In the container model, a container image represents a process, so the process launched by the Dockerfile’s ENTRYPOINT determines the container’s lifecycle. Orchestrators such as Kubernetes use process exit codes and application health checks such as livenessProbe and readinessProbe to determine whether an application has exited normally and whether to restart or terminate it. Container orchestration will be covered in a separate document.

The Technology Behind Docker

https://stackoverflow.com/questions/16047306/how-is-docker-different-from-a-virtual-machine

Image Layers

When Docker builds an image, the result—the delta—of each Dockerfile command is added as a layer with a unique identifier. Layers are easy to cache because the same context produces the same result. For this reason, place commands that change frequently as late as possible in the build.

Dockerfile

A Dockerfile is the most common way to create a Docker container image. See the Dockerfile reference for details.

.dockerignore

When you run docker build, files matching patterns in .dockerignore are excluded from the context sent to the Docker daemon. It works similarly to .gitignore.

Where possible, exclude directories such as node_modules, whose contents vary by environment, as well as code and resources not used by the application itself, such as deployment scripts and configuration files. Even Dockerfile and .git are often excluded.

For Go projects that include all dependencies in vendor, it is better not to exclude that directory.

Networking

TBD

Volumes

TBD

Building Docker Images for Deployment

Processes

As noted above, think of a Docker container as corresponding to a process. ENTRYPOINT defines the process that determines the container’s lifecycle. It is possible to launch several processes inside one container and manage them with a tool such as supervisor, but this is not the usual approach and makes it harder to benefit from container orchestration. The runtime environment can override ENTRYPOINT, for example through Kubernetes’ command setting.

Environment Variables

TBD

Optimizing Image Size

Large Docker images take longer to push to a registry after a build and to pull into a deployment environment. They also increase registry storage and data-transfer costs. Keep deployment images as small as possible.

Use Lightweight Base Images Such as alpine or slim

Alpine Linux is an extremely lightweight distribution, with an image size of around 5 MB. An Alpine-based Python image is around 29 MB, more than ten times smaller than the default Debian-based Python image at 356 MB.

Making Better Use of Layer Caching

COPY SOME_FILE .
RUN chown app:app SOME_FILE

If you add files and change their ownership in the next layer as shown above, the first layer contains the added files, and the ownership change adds another layer of the same size.

COPY --chown app:app SOME_FILE

Instead, optimize the build by setting ownership in the COPY command, as shown above. Where possible, combine multiple RUN steps into one layer.

Use docker history IMAGE to inspect the size of each layer in a built image.

Caching with Multi-Stage Builds

Multi-stage builds let you keep only the build output in the final image, leaving out libraries used solely for building. For example, you can build a JAR in an image containing Gradle and copy it into a JDK container.

Dockerfile Guidelines by Language

golang

A compiled Go binary can be deployed on its own without source code or dependency packages. Build the binary using a golang image, then copy only the binary into a scratch image, which adds no extra layer of its own. This produces a lightweight image close to the size of the binary for use in build and deployment environments.

FROM golang:1.1-alpine as builder
RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates
RUN adduser -D -g '' app
WORKDIR $GOPATH/src/path/to/package
COPY . .
RUN go build -o /go/bin/binary_name

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /go/bin/binary_name /go/bin/binary_name
USER app
ENTRYPOINT ["/go/bin/binary_name"]

python

We recommend Alpine images for Python as well. However, codebases already running in Debian-based containers currently use slim images because of compatibility issues with Alpine packages. pip install can take a long time and produces a fairly large layer. To maximize caching, add only the requirements file first, install packages, and then add the remaining application code.

FROM python:3.7.2-alpine
RUN apk --no-cache add build-base libffi-dev openssl-dev
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . ./

nodejs

FROM node:10.12-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD ["npm", "start"]

Static Websites

A static website does not need Node.js or node_modules in its deployment environment. Use a multi-stage build to build the site, then add only the output to an Alpine nginx base image (9 MB) to serve the static content.

FROM node:8-alpine AS builder

WORKDIR /app
COPY package*.json /app/
RUN npm install

COPY . /app
RUN npm run build

FROM nginx:1.15-alpine
COPY --from=builder /app/build /usr/share/nginx/html

https://docs.docker.com/develop/develop-images/multistage-build/

Container Security Considerations

Reduce User Privileges

Just as a Linux service such as nginx or postgres generally runs under a dedicated user with only the permissions it needs, create a service user inside the container so the application cannot acquire unnecessary privileges.

Use the Lightest Practical Base Image

An image with many tools and libraries gives anyone who gains execution access inside the container more options. Include only libraries required to run the application.

It may be tempting to install curl, a feature-rich bash shell, networking tools, or even vim for later debugging, but leave them out unless the application requires them. In production, make the most of profilers, APM, log collection, and distributed tracing instead.

Be Careful with Public Images

Avoid using images from public registries such as Docker Hub in production unless they are officially maintained by the relevant project. We do not yet manage base images separately, but are considering maintaining our own in the future.

GCR Vulnerability Scanning

GCR automatically scans pushed images based on Debian, Ubuntu, and Alpine, primarily checking for known vulnerabilities in system packages. For application packages and libraries, use GitHub’s security alerts.

Google Cloud Build

Google Cloud Build is a convenient, fast CI option for building container images. We use it for continuous image builds, with additional features such as repository security scanning.

If a repository contains a Dockerfile, a simple trigger configuration can connect it to Cloud Build. Commits or tag pushes can trigger an image build and upload to GCR.

To speed up builds, separate installation of system packages, pip packages, and Node modules into their own stage. Use --cache-from when building a new image to reuse the base image from a previous build as a cache. To use this approach, define the build steps in a YAML file, cloudbuild.yaml. (Reference)

steps:
  - name: 'gcr.io/cloud-builders/docker'
    entrypoint: 'bash'
    args:
    - '-c'
    - |
      docker pull asia.gcr.io/$PROJECT_ID/${_SVC_BASENAME}/base || true
  - name: 'gcr.io/cloud-builders/docker'
    entrypoint: 'bash'
    args:
    - '-c'
    - |
      docker build \
        --cache-from asia.gcr.io/$PROJECT_ID/${_SVC_BASENAME}/base \
        --tag asia.gcr.io/$PROJECT_ID/${_SVC_BASENAME}/base \
        -f Dockerfile.prod \
        --target base \
        .
      docker build \
        --cache-from asia.gcr.io/$PROJECT_ID/${_SVC_BASENAME}/base \
        --tag asia.gcr.io/$PROJECT_ID/${_SVC_BASENAME}:$TAG_NAME-$SHORT_SHA \
        -f Dockerfile.prod \
        .
images:
  - 'asia.gcr.io/$PROJECT_ID/${_SVC_BASENAME}/base'
  - 'asia.gcr.io/$PROJECT_ID/${_SVC_BASENAME}:$TAG_NAME-$SHORT_SHA'
timeout: '1200s'

Tagging Docker Images

If you do not specify a tag when building a Docker image, it defaults to latest. It is easy to assume that using latest always gives you the newest image, but reusing the same tag for images with different contents makes container orchestration and deployment management harder.

We therefore recommend assigning a static, immutable tag representing a specific release state—such as a commit SHA, branch name, or tag name—to every release.

Docker Registries

Public and Private Registries

ECR and GCR

Image Cleanup

TBD

Local Development with Docker

docker-compose

TBD

skaffold

TBD

All guides
Handbook

API Definition

This document explains how to define and use APIs (application programming interfaces) at Buzzvil. Interfaces are defined using Protocol Buffers. Guidelines API-First Approach Use…

Handbook

Buzzvil Architecture

This website brings together an overview of Buzzvil’s software architecture and the documentation needed to develop within it. Introduction The advertising ecosystem is complex bec…

Handbook

Service Deployment

This document describes what you need to prepare to deploy backend services at Buzzvil. Guidelines The process for deploying services to Kubernetes at Buzzvil is shown below. Altho…