golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.12k stars 17.68k forks source link

x/pkgsite: Dockerfile.frontend should use a scratch base image #48605

Open sfllaw opened 3 years ago

sfllaw commented 3 years ago

Problem

Currently, devtools/docker/Dockerfile.frontend uses the golang image as its base image.

This is inconsistent with modern best practices, where services are typically hosted on the minimal scratch image. The current image has an entire operating system and the Go toolchain included, which is unnecessary because only the frontend and its data are copied in. The frontend doesn’t need an entire OS to run.

Suggested solution

  1. Build cmd/frontend with CGO_ENABLED=0 to produce a statically linked binary.
  2. Use FROM scratch to build the final Docker image.

Links

Related: #39827

sfllaw commented 3 years ago

Here is a sample devtools/docker/Dockerfile.frontend that works for me:

# Copyright 2021 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

# This Dockerfile expects the build context to be the public repo root.

################################################################
FROM golang:1.16.7 AS builder

# Set the working directory outside $GOPATH to ensure module mode is enabled.
WORKDIR /src

# Copy go.mod and go.sum into the container.
# If they don't change, which is the common case, then docker can
# cache this COPY and the subsequent RUN.
COPY go.mod go.sum all.bash /

# Download the dependencies.
RUN go mod download

# Copy the pkgsite repo from local machine into Docker client’s current working
# directory, so that we can use it to build the frontend.
# See .dockerignore at the repo root for excluded files.
COPY . /src

# Build the frontend.
RUN CGO_ENABLED=0 go build -mod=readonly ./cmd/frontend

################################################################
FROM busybox:1.32-musl AS busybox

FROM scratch

WORKDIR app

COPY --from=busybox /bin/sh /bin/
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

COPY --from=builder src/frontend      frontend
COPY static                           static
COPY third_party                      third_party
sfllaw commented 3 years ago

The above Dockerfile as a diff:

diff --git a/devtools/docker/Dockerfile.frontend b/devtools/docker/Dockerfile.frontend
index a35f73da..f17135ac 100644
--- a/devtools/docker/Dockerfile.frontend
+++ b/devtools/docker/Dockerfile.frontend
@@ -6,7 +6,6 @@

 ################################################################
 FROM golang:1.16.7 AS builder
-# If you change the Go version above, change the FROM line below as well.

 # Set the working directory outside $GOPATH to ensure module mode is enabled.
 WORKDIR /src
@@ -25,13 +24,18 @@ RUN go mod download
 COPY . /src

 # Build the frontend.
-RUN go build -mod=readonly ./cmd/frontend
+RUN CGO_ENABLED=0 go build -mod=readonly ./cmd/frontend

 ################################################################
-FROM golang:1.16.7
+FROM busybox:1.32-musl AS busybox
+
+FROM scratch

 WORKDIR app

+COPY --from=busybox /bin/sh /bin/
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+
 COPY --from=builder src/frontend      frontend
 COPY static                           static
 COPY third_party                      third_party
mitar commented 2 years ago

Are you sure scratch is enough and not something like gcr.io/distroless/static?

sfllaw commented 2 years ago

@mitar That is a good question. Since frontend relies on GOPROXY, having the CA certificates baked in to the image might be very nice.

sfllaw commented 2 years ago

Also, it would probably be best to add an ENTRYPOINT statement in there: https://github.com/docker-library/official-images#consistency:

If the image only contains the main executable and its linked libraries (ie no shell) then it is fine to use the executable as the ENTRYPOINT, since that is the only thing that can run:

ENTRYPOINT ["fully-static-binary"]
CMD ["--help"]

The most common indicator of whether this is appropriate is that the image Dockerfile starts with scratch (FROM scratch).