Embracing Minimalist Containerization
In the realm of software development, the deployment and management of applications have been transformed by containerization. Containers, fundamentally different from virtual machines, provide a lightweight, efficient, and scalable solution for running multiple applications on the same operating system kernel.
Containers vs. Virtual Machines
Unlike virtual machines, containers do not encapsulate a full operating system; instead, they package only the necessary components required to run the application, sharing the host system’s OS kernel. This shared approach results in reduced overhead and faster startup times compared to VMs which require their own OS instance.
The Power of “Scratch” and Minimal Footprints 🤘
A scratch
container is essentially an empty image, providing the minimal environment for executing applications. This is particularly effective for applications compiled into self-executable binaries, such as those written in Go. Here is an example Dockerfile illustrating how to create a lightweight container using a scratch base:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Dockerfile Scratch
FROM golang:1.22.1-alpine as build
RUN mkdir /src
ADD ./dynamicWelcomeService/*.go /src
ADD ./dynamicWelcomeService/go.mod /src
ADD ./dynamicWelcomeService/go.sum /src
WORKDIR /src
RUN go get -d -v -t
RUN GOOS=linux go build -v -o dynamicWelcomeService
RUN chmod +x dynamicWelcomeService
FROM scratch
COPY --from=build /src/dynamicWelcomeService /usr/local/bin/dynamicWelcomeService
EXPOSE 8080
CMD ["dynamicWelcomeService"]
Choosing the Right Minimal Base Images
For those needing slightly more than what scratch offers, minimal Linux distributions like Alpine or Wolfi are excellent alternatives. They still provide a minimal footprint while including some essential libraries and utilities that are absent in scratch. Here’s how you might set up a Docker container using Alpine as a base:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Dockerfile Alpine
FROM golang:1.22.1-alpine as build
RUN mkdir /src
ADD ./dynamicWelcomeService/*.go /src
ADD ./dynamicWelcomeService/go.mod /src
ADD ./dynamicWelcomeService/go.sum /src
WORKDIR /src
RUN go get -d -v -t
RUN GOOS=linux go build -v -o dynamicWelcomeService
RUN chmod +x dynamicWelcomeService
FROM alpine:3.19.1
COPY --from=build /src/dynamicWelcomeService /usr/local/bin/dynamicWelcomeService
EXPOSE 8080
CMD ["dynamicWelcomeService"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Dockerfile Wolfi
FROM golang:1.22.1 as build
RUN mkdir /src
ADD ./dynamicWelcomeService/*.go /src
ADD ./dynamicWelcomeService/go.mod /src
ADD ./dynamicWelcomeService/go.sum /src
WORKDIR /src
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o dynamicWelcomeService
RUN chmod +x dynamicWelcomeService
FROM ghcr.io/chainguard-images/static:latest
COPY --from=build /src/dynamicWelcomeService /usr/local/bin/dynamicWelcomeService
EXPOSE 8080
CMD ["/usr/local/bin/dynamicWelcomeService"]
Avoiding Larger Base Images 🙅
While convenient, larger images such as Debian and Ubuntu carry more than what is often necessary for running a simple application, resulting in larger image sizes and potential security risks from additional, unnecessary software. A minimal approach minimizes these risks and improves deployment speeds.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Dockerfile Debian
FROM golang:1.22.1-alpine as build
RUN mkdir /src
ADD ./dynamicWelcomeService/*.go /src
ADD ./dynamicWelcomeService/go.mod /src
ADD ./dynamicWelcomeService/go.sum /src
WORKDIR /src
RUN go get -d -v -t
RUN GOOS=linux go build -v -o dynamicWelcomeService
RUN chmod +x dynamicWelcomeService
FROM debian:stable-20240311
COPY --from=build /src/dynamicWelcomeService /usr/local/bin/dynamicWelcomeService
EXPOSE 8080
CMD ["dynamicWelcomeService"]
Conclusion
Selecting the right base image for containers is crucial in optimizing both the performance and security of applications. Linux images such as Scratch, Alpine or Wolfi provide an optimal environment for many applications, particularly those compiled into binaries. By understanding and utilizing these minimal environments, developers can significantly enhance their deployment strategies in the containerized world.