April 27, 2023·4 min read·engineeringdockerdocker-initdeveloper-tools

Docker init extends generated container configuration to Python and Node.js

Desktop 4.19 adds Python and Node.js to docker init. Generating the build files makes their dependency structure and runtime assumptions the next engineering decisions.

Adding containers to an existing application starts with decisions that are easy to confuse with file creation. A Dockerfile needs to describe construction of the image, while the running service needs its own invocation and configuration. Docker Desktop 4.19, released April 27, expands the beta docker init command to Python and Node.js. The command addresses the initial creation of those assets, leaving developers with files they can inspect and maintain.

The distinction matters because writing a file is only the beginning of defining an environment. A generated Dockerfile can give a project a coherent starting point. Its usefulness then depends on whether the application actually follows the assumptions in that file. Language support reduces the amount of initial translation a developer must perform between an application and its container configuration.

I like this direction because a repeatable build file is one of Docker's most useful contributions. The file becomes a concrete description that somebody else can read. Generation is valuable when it gets that description into the project sooner and leaves its decisions visible to the people maintaining the application.

The command creates project assets

Docker introduced the beta earlier this month. Its April 5 announcement describes automatic creation of Dockerfiles, Compose files and a .dockerignore file. The first beta included Go support. Today's release adds two more language ecosystems to that project-initialization workflow, while retaining the beta status.

The invocation is short and runs inside the target project directory. This is the command from the earlier announcement, applied to a project supported by the installed Desktop version.

docker init

Its output belongs to the application configuration. That makes the result different from an opaque environment maintained outside the project. A developer can read the proposed build steps and decide how they relate to the application. Later changes to dependencies or startup behavior can be expressed in the same files.

The earlier announcement also distinguishes this command from the internal docker-init executable associated with docker run --init. Project initialization and process initialization are separate operations. The similar names should not lead a developer to expect one command to change the container's process behavior merely because it generated a Dockerfile.

Dependency installation defines a cache boundary

Docker's earlier Dockerfile guidance for Node.js explains why copying dependency manifests before application source matters. Its example copies package.json and package-lock.json, runs npm ci, then copies source files. Changes to source need not force the dependency-installation step to run again when its inputs remain unchanged.

That relationship is a useful way to evaluate any starting Dockerfile. Dependency installation consumes a specific set of inputs. Copying unrelated files before that operation expands the set of changes that can invalidate it. The structure of the build consequently affects how much work is repeated during an ordinary edit and rebuild.

A generated configuration is useful partly because it makes that relationship available for inspection. The question is whether the actual dependency inputs are present at the right step. A project with additional installation inputs needs to express them. The fact that a language has a common package layout does not establish that every repository uses precisely that layout.

This is also why a Dockerfile should remain understandable after generation. A maintainer who knows what an installation step consumes can change the build when the dependency arrangement changes. A maintainer who treats the file as unexplained output has less information when a source edit unexpectedly causes a much larger rebuild.

Python has the same ordering problem

Docker's Python container-development example makes a similar distinction. It installs from requirements.txt before copying application source. The example separates the dependency file from the source directory so the dependency installation can remain reusable across source changes. This predates the new initialization command and explains one of the design problems its output must represent.

The language-specific command differs, but the dependency relationship is familiar. A Python service needs an interpreter and installed packages before its startup command can run. Its source files then need to occupy the location expected by that command. These are connected decisions in the image definition, not independent snippets that become correct merely by appearing in the same file.

The Python article also demonstrates separate build stages for producing a smaller runtime image. That technique makes the transfer of runtime requirements explicit. It is relevant when a dependency-installation environment contains more than the final service needs. A project still has to identify the files and runtime components its application requires.

My preference for a small final image follows the same reasoning. A static Go service can make that final inventory unusually simple. Python and Node.js applications have different runtime requirements, so the useful goal is an explicit inventory appropriate to the application. A language-aware initializer can start that description without making the runtime decision disappear.

Generated files become maintained configuration

The release also updates Desktop's Engine and CLI to Moby 23.0. That change is separate from the initializer's language support. One changes the shipped tools, while the other changes which projects can receive generated assets. Keeping them distinct helps explain what an upgrade provides to a project that already has a Dockerfile.

An existing project may have no need to regenerate its configuration. Its files already express decisions about construction and execution. For a project adding containers, the initializer reduces the initial work of establishing those files. Both cases still benefit from knowing which command builds the image and which configuration starts the application. That information also helps a new contributor distinguish the service's runtime requirements from tools used only while building it.

The beta's expanded language coverage therefore affects the point where container support enters a project. After that point, dependency changes and application behavior remain visible maintenance work. A source layout change may alter what the build copies. A startup change may alter the command the container needs to execute. Keeping the generated assets with the application lets those changes be reviewed together.