Dev Containers Demo!
This repository provides a sample application with multiple development container configurations. The configurations have been tested to work with Visual Studio Code and Codespaces. The images support ARM64 (including macOS M1) and AMD64 (Intel x86/x64).
Branches
There are multiple branches in this repository. Each represents a different container configuration.
main. Default branch. Contains a simple JavaScript application with no container configuration. This provides a baseline for development and allows Codespaces to be used with the default Codespaces universal container.bare. Same content asmainfor ease of comparison if you are creating and customizing your own container.image. The default configuration created by VS Code 1.73.1+. This references an image and does not use a local Dockerfile for configuration.alpine. A minimal container using Alpine. Alpine does not support users, so it runs asroot.dockerfile. Uses the default Dockerfile configuration created by older versions of VS Code.compose. Development container using Docker Compose for the development environment and a Redis stack server.
Application
The application is a basic TypeScript web application running on Node.js (18.x). The TypeScript is transpiled into JavaScript which is placed in the dist folder. The application has two endpoints:
/. Displays an incrementing counter./reset. Resets the counter to zero.
By default, the application uses an in-memory counter. The application state is lost when the application is restarted. If Redis is available (such as when Docker Compose is used), it will be used to store the counter state. In this case, the count is preserved between restarts.
A configuration file is provided which configures code debugging in Visual Studio Code, allowing you to step into the application and set breakpoints.
Compatibility
The images used (18-bullseye and alpine) support both Intel and ARM64 devices. This enables the containers to use the native processor in both local and cloud environments.
Configuration Details
This provides detail about the configuration files (.devcontainer/devcontainer.json) used by each branch.
Main
A code-only branch that can be used as a starting point for creating and using dev containers.
Bare
The code-only branch. There is no dev container. It is identical to main, making it useful for comparisons if main is altered.
Image
The default configuration for a dev container for Node.js. This configuration is unmodified and creates a working environment for developing with Node.js. The complete workspace is mounted, so changes in the container are persisted to the host.
This container can generate a warning when using Git operations: Git: fatal: detected dubious ownership in repository. This can be corrected by automatically configuring the workspaces folder as a safe directory in your dotfiles repository or using code. The code approach is used for the non-default images.
Alpine
An environment variable is configured in the container (PORT) to ensure the application runs on port 8000. It uses portAttributes to apply the label Express to this port.
It has two mounts configured:
tmpfsmount to/{workspace}/dist. Thedistfolder is used to hold the JavaScript that is generated by the TypeScript. This mount improves the performance of read/writes to that folder and prevents the generated files from being stored on the host filesystem.jsdemo-node-modulesmount to/{workspace}/node_modules. This stores the NPM packages in a Docker volume, caching the contents between runs. None of the files are written to the host. The files can be purged by deleting the volume.
These mounts are not required and can be removed. In that case, the folders will be created in the container and shared with the host file system.
The following lifecycle commands are used:
postStartCommand. Configures the client workspace as a "safe directory" for Git. This ensures the file system is trusted for Git operations. This avoids possible warnings from Git.postCreateCommand. Runs NPM install to load the packages. This is a convenience to improve the developer experience.onCreateCommand. Purges thedistfolder. This is not required, but it ensures the folder is initially empty. If the TypeScript is compiled outside of the dev container, this folder could contain files. Docker would initialize the in-memory volume with those files. Removing the files after the container is created ensures any existing files are untouched on the host file system while providing a clean filesystem for the container.
The container includes the following VS Code extensions:
dbaeumer.vscode-eslint. Code linter.tomoki1207.pdf. Supports opening and viewing PDF files.ms-azuretools.vscode-docker. Adds Dockerfile support.
A Dockerfile is referenced. This file uses the Alpine image. In addition, it updates NPM to the latest version to prevent warnings when restoring packages. This demonstrates the ability of dev containers to use custom, optimized Dockerfile (or images) to improve performance or the developer experience. In this case, the image is 165 MB (compared to nearly 1.6 GB for mcr.microsoft.com/vscode/devcontainers/typescript-node:18-bullseye). This creates a faster start, improved security, and lower memory requirements.
The .vscode folder contains the launch configuration for debugging. In addition, it adds some tasks for developing in TypeScript.
Dockerfile
This is similar to alpine, but using the larger developer image provided by Microsoft. It also includes netcat and redis-tools to support interactions with an optional external Redis instance.
The container runs as the user node with reduced privileges. Because volume mounts are created using a privileged identity, the node_modules mount will initially be created as root. This would prevent Node.js from successfully installing any packages. To avoid this, postCreateCommand uses chown to change the folder's owner to node.
Compose
This builds on the previous example and uses Docker Compose to create multiple containers. The additional container runs redis-stack to provide a full-feature Redis environment. The Redis Dashboard is exposed on port 8001. It is labelled using portAttributes. Redis is exposed to the container network on port 6379. A data volume, jsc-redis-data is mounted to /data to persist the counter data.
The development container is exposed as the service app. The associated container is given a workspace path /workspaces/demo
The container includes two additional VS Code extensions:
EditorConfig.EditorConfig. Enables VS Code to use the.editorconfigfile to enforce coding standardsjbockle.jbockle-format-files. Enables VS Code to format all files in the workspace.
The mounts are defined in the docker-compose.xml.