Challenge,ย Medium, ย onย  Containers,ย Programming

You are tasked with optimizing the container image for a Go application. The application is a simple web service with a front page and a few API endpoints. The service uses an SQLite database to store the visitor counter, and since SQLite is a C library, the application is dynamically linked.

The source code of the application is located in the ~/app directory, along with an initial Dockerfile. You can use the make docker-build command to build the image and make docker-run to run the container. However, the current setup does not adhere to containerization best practices. Your goal is to improve the Dockerfile and create a production-ready container image.

Build a Docker image named registry.iximiuz.com/app:v1.0.0 that meets the following requirements:

  1. Base Image:
  • Use a minimalistic base image.
  • All application's dependencies must be satisfied.
  1. Security:
  • The image must run as a non-root user.
  • Ensure there is no shell in the image.
  1. Performance:
  • The final image size must not exceed 40 MB.
  1. Runtime:
  • The application must:
    • Listen on 0.0.0.0:3000.
    • Include all HTML files from the templates folder.
    • Include all assets from the static folder.
    • Serve APIs and static files correctly.
  1. Testing:
  • Verify the application by running a container and checking the main page and the API handlers responses.

The final solution will be validated using automated checks. You can access dynamic hints from the checks by clicking the task box below.

Good luck! ๐Ÿš€

Hint 1 ๐Ÿ’ก

If you're new to Docker, you may want to solve this simpler challenge first: Build and Publish a Container Image With Docker

Hint 2 ๐Ÿ’ก

The problem with the application's original Dockerfile is that it includes the dev- and build-time dependencies in the final image. It includes not only the Go compiler, but also hundreds of other packages and tools that are not needed at runtime but increase the image size and the attack surface:

Single-stage Go image build: The resulting runtime image contains the entire Go compiler toolchain.

To separate the build- and runtime dependencies, a multi-stage Docker build can be used:

Multi-stage Go image build: The build stage installs all dependencies, while the runtime stage only includes the production dependencies.

If you're not familiar with this technique, check out this tutorial on how to produce smaller container images with multi-stage builds.

Hint 3 ๐Ÿ’ก

The Go application in this challenge is dynamically linked (i.e., depends on the system-wide libc, the sqlite3 C library, and potentially other external libraries), so choosing the right base image for the runtime stage is going to be trickier than for a statically linked application.

For instance, the (quite popular) FROM scratch approach won't work unless you're willing to carefully craft a custom FROM scratch image adding not only the necessary Linux rootfs layout and the certificate authority (CA) bundle, but also the required C libraries.

Switching to a minimalistic but full-fledged Linux distro like alpine won't do the trick either because of the requirement not to include a shell in the image.

Distroless base images to the rescue:

Distroless container image hierarchy: minimalistic base images that include only the necessary system files.

But be careful: not all distroless images are suitable for a dynamically linked application.

Level up your Server Side game โ€” Join 9,000 engineers who receive insightful learning materials straight to their inbox