Ken Muse

Dev Containers, Codespaces, and Bicep, Oh My!

If you’re not familiar with Development Containers from my previous post, they provide a powerful way to share a fully operational development environment with others. Visual Studio Code provides support for devcontainers using a set of configuration files that describe the environment. These environments can be automatically loaded and configured by anyone running VS Code. In addition, the same environment definition works in GitHub Codespaces, enabling a cloud-hosted development experience.

To work locally, Windows users will want to enable WSL and then install Docker Desktop. A complete walkthrough is available in the containers tutorial, so I won’t dive too much into that process.

Today, we’re going to quickly walkthrough how to setup a container for Bicep development. Normally, I prefer to use Alpine (smaller footprint) and a container running as a restricted user. That said, sometimes we can take advantage of one of the existing containers to simplify the experience. Bicep is still working to get to a v1.0, so the overall experience is constantly improving. As a result, I expect this process will only get easier in the future.

The process requires us to first create a folder, .devcontainer at the root of our project. This should contain two files – devcontainer.json and Dockerfile.

The Dockerfile

The Dockerfile defines the environment that will host the extension and be used for development. In order to support Bicep, the environment requires a few components:

  • PowerShell Core 7.2. To support this, .NET 6 will need to be installed. The .NET runtime is also needed for the Bicep extensions in VS Code to work properly. PowerShell can be installed as a global dotnet tool, simplifying the setup.
  • The Azure CLI which supports deploying code to Azure from the command line, among other things. Setup involves downloading and running a bash setup script.
  • Bicep executable. This can be automatically installed by the Azure CLI the first time it’s used, but we’ll configure it to ensure it is available to the VS Code extension.

Of course, you’re free to add additional tools, such as Node.js or the GitHub CLI. To keep things simple, we’ll start with a Microsoft-provided Development Container image. It’s not required, but it is already optimized for using .NET 6 in VS Code. Next, we install each of the prerequisites. The image defaults to running as root, so sudo is not needed for any of the commands. The image also includes a vscode account, so it supports running securely as a non-root user.

The basic Dockerfile looks like this:

 6# Install PowerShell and  Azure CLI [requires elevated permissions]
 7RUN dotnet tool install -g powershell --version $POWERSHELL_VERSION \
 8 && curl -sL | bash
10# Install Bicep [requires elevated permissions]
11 RUN curl -Lo /tmp/bicep${BICEP_VERSION}/download/bicep-linux-x64 \
12    && chmod +x /tmp/bicep \
13    && mv /tmp/bicep /usr/local/bin/bicep

The development container configuration

The devcontainer.json defines this as a Development Container and provides configuration details required for setting up VS Code. This requires a few spsecific settings as well. The two most important:

  • The build definition, pointing to the Dockerfile
  • A list of extensions required by VS Code. At a minimum, the following tools are needed:
    • ms-dotnettools.vscode-dotnet-runtime. Ensures the appropriate .NET runtime is configured for Bicep.
    • ms-azuretools.vscode-bicep. The Bicep editor extension.
    • Integrates the Code environment with Azure to enable deployment, providing a single approach for Azure sign-in and subscription filtering. This also enables Code to use the Cloud Shell service (optional).
    • msazurermtools.azurerm-vscode-tools. The ARM template editor extension, which provides support for editing any generated ARM templates (optional).
 1// For format details, see
 3	"name": "Bicep",
 4	"build": {
 5		"dockerfile": "Dockerfile",
 6	},
 8	// Any default configuration settings needed
 9	"settings": {},
11    // The most important extensions for Bicep Development
12	"extensions": [
13		"ms-dotnettools.vscode-dotnet-runtime",
14		"",
15		"ms-azuretools.vscode-bicep",
16		"msazurermtools.azurerm-vscode-tools"
17	],
19    // Restrict the total memory consumed by the container.
20	"runArgs": [
21		"--memory=1.2g"
22	],
24	"remoteUser": "vscode"

This file is straightforward, but I want to highlight two items. First, you can specify runtime arguments for the Docker container, enabling you to specify important settings. In this case, I’m choosing to apply a resource constraint on the available memory. I’ve noticed the Bicep extension can consume all available memory. To avoid that, I’d rather have the container restrict the memory consumption. While it can result in a need to restart the container, it protects my host environment from running out of memory.

The second highlight is that the base image used in the Dockerfile runs as root by default, but supports a vscode user with restricted permissions. I prefer to always run in a more restricted environment when possible.

Similar to the Dockerfile, you can further extend this environment. Some tools I prefer to include:

  • redhat.vscode-yaml. Adds support for editing YAML files.
  • ms-azuretools.vscode-docker. Enables editing Dockerfiles. Enables building, managing, and deploying containerized applications. It also adds debugging support for code running in containers.
  • editorconfig.editorconfig. Enables support for EditorConfig in VS Code. This specification allows teams to maintain a consistent coding style across IDEs.
  • ms-vscode.powershell. Enables cross-platform PowerShell language support.
  • alefragnani.Bookmarks. Sometimes it can be incredibly helpful to be able to bookmark locations in a file, especially when refactoring. This extension adds support for that to Code.
  • heaths.vscode-guid. I’m always finding myself needing an ability to quickly insert a new Guid value or UUID. This allows you to quickly insert a value in any common format.

The developer experience

That’s it. A single folder and those two files creates a complete, working Bicep development environment that runs in VS Code or in GitHub Codespaces. This environment can be utilized by everyone working in the project, ensuring that each person has the same experience and all necessary tools. In this case, it also eliminates the complexity of setting up Bicep or creating an environment which can be used for deploying Azure rsources.

Update: Feel free to fork the sample repo on GitHub to experience having a completely configured environment running in Codespaces or VS Code.