Challenge, Medium,  on  Containers

Write a Dockerfile with Sensible Runtime Defaults

In this challenge, you will write a Dockerfile for a simple web service and learn how Docker images can improve the application startup UX by defining sensible runtime defaults.

In Docker, many runtime parameters can be provided when starting (or stopping) a container: environment variables, the runtime user, and even the signal sent to the application to stop it. However, one of the biggest UX improvements Docker brought was the ability to bake sensible defaults into the image itself. That way, a simple docker run <image> is often enough to start the application correctly, and an argumentless docker stop <container> is enough to shut it down gracefully.

Explore the application

The application you need to package is located in the ~/service/ directory. It's a Node.js HTTP service that uses only built-in modules - no external dependencies needed.

Take a moment to explore the code and see how it works:

cd ~/service
cat index.js

The service starts an HTTP server on the port defined by the PORT env var and uses a couple of runtime parameters to control its behavior - LOG_LEVEL and ENVIRONMENT.

The task

Your job is to package the service into a container image so that these parameters have sensible defaults baked into the image itself.

The image should define:

  • Default environment variables for the application configuration:
    • PORT = 8080
    • LOG_LEVEL = debug
    • ENVIRONMENT = development
  • A non-root user for running the service
  • A stop signal that allows the application to shut down gracefully

Create a Dockerfile in the ~/service/ directory and build a container image named service:v1.0.0.

Dockerizing Node.js applications

For a refresher on how to Dockerize a Node.js application, check out the Write Your First Dockerfile challenge.

The final image should be prepared in such a way that a plain docker run command is enough to start a fully configured service:

docker run -p 8080:8080 service:v1.0.0

Once the container is running, you should be able to inspect the service with:

curl localhost:8080

The response should reflect the default configuration baked into the image and show that the application is running under a non-root user.

Setting default environment variables

The ENV instruction sets environment variables that persist in the image and are available to the running container:

~/service/Dockerfile
ENV MY_VAR=my_value

Note that these defaults can still be overridden at runtime with docker run -e flag(s).

Running as a non-root user

The typical way to run a container as a non-root user is to create a system user and switch to it. Use a RUN instruction to create a system user and then the USER instruction to switch to it:

~/service/Dockerfile
RUN addgroup --system mygroup && \
    adduser --system --ingroup mygroup myuser

USER myuser

However, some base images can already have a non-root user. For instance, the node:24-slim image has a node user, which you can use a shortcut instead of creating a new one:

~/service/Dockerfile
USER node

When the container is stopped with the usual docker stop command, the application should be able to shut down gracefully.

What signal does docker stop send?

By default, docker stop sends SIGTERM to the container's main process. However, some applications handle a different signal for graceful shutdown.

The STOPSIGNAL instruction in a Dockerfile lets you change which signal docker stop sends:

~/service/Dockerfile
STOPSIGNAL <signal>

Check the application code to see which signal it handles for shutdown.