{"id":15406,"date":"2024-04-26T00:00:00","date_gmt":"2024-04-26T07:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/ise\/?p=15406"},"modified":"2024-07-18T11:58:45","modified_gmt":"2024-07-18T18:58:45","slug":"dev-containers","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/dev-containers\/","title":{"rendered":"Development Containers Simplified"},"content":{"rendered":"<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2024\/04\/dev-container-stages.png\" alt=\"load-testing\" \/><\/p>\n<h2>Context<\/h2>\n<p>At the start of every new project, we always face many challenges when setting up our development environment or onboarding a new team member. We have to install and configure many tools and dependencies to get the development environment up and running locally. In addition, dealing with different development machine configurations (OS, CPU, file system, etc.) or working across different IDEs (VS Code, IntelliJ, Codespaces, etc.) can be a real challenge. Finally, having a solution for the most famous statement in the software development world: &#8220;It works on my machine&#8221;.<\/p>\n<p>In this post we will explore the concept of development containers and how it can help us to solve the above challenges. <em>You can find a full code sample <a href=\"https:\/\/github.com\/ISE-Neutrino\/api-testing\">here<\/a><\/em><\/p>\n<h2>What is a development container?<\/h2>\n<p><strong>Development containers (or dev containers)<\/strong> are Docker containers that are specifically configured to provide a full-featured development environment. As containers become more popular in the software development world, the same concept is used to create a container that contains all the tools and dependencies needed to run our development environment. This container can be used by all team members to run the development environment locally (Inner loop), without the need to install any tools or dependencies on their machines or extend it to be used in the CI\/CD pipeline (Outer loop) to build, test and deploy your application.<\/p>\n<h2>Dev containers support<\/h2>\n<p>Dev containers are supported by many IDEs until now and the list is growing. Some of the most popular IDEs that support dev containers are:<\/p>\n<ul>\n<li><a href=\"https:\/\/code.visualstudio.com\/docs\/devcontainers\/containers\">Visual Studio Code<\/a><\/li>\n<li><a href=\"https:\/\/www.jetbrains.com\/help\/idea\/connect-to-devcontainer.html\">IntelliJ<\/a><\/li>\n<li><a href=\"https:\/\/docs.github.com\/en\/codespaces\/overview\">Github Codespaces<\/a><\/li>\n<\/ul>\n<p>This post focuses on VS Code as it has a matured Dev container support and become more popular IDE, and we already use it in our last engagement.<\/p>\n<h2>Getting started<\/h2>\n<p>In this section we will build a sample dev container for a Java application. The container will contain the following tools and dependencies:<\/p>\n<ul>\n<li>Java 17<\/li>\n<li>Maven<\/li>\n<li>Git<\/li>\n<li>VS Code extensions<\/li>\n<\/ul>\n<blockquote>\n<p>Note: <a href=\"https:\/\/docs.docker.com\/engine\/install\/\">Docker<\/a> is required to build and run the dev container, In addition if you&#8217;re using Windows, you need to install <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/wsl\/install\">WSL2<\/a> to run dev containers.<\/p>\n<\/blockquote>\n<h3>devcontainer.json<\/h3>\n<p>The definition of the development container is stored in a JSON (with comments) file called <code>devcontainer.json<\/code>. The file follows the <a href=\"https:\/\/containers.dev\/implementors\/json_reference\/\">dev containers specification<\/a>. This file contains the metadata that is used to create the dev container.<\/p>\n<p>While the structure of this metadata is critical, it is also important to call out how this data can be represented on disk where appropriate. IDEs using it should expect to find a <code>devcontainer.json<\/code> file in one or more of the following locations (in order of precedence):<\/p>\n<pre><code class=\"language-bash\"># Main repo directory\r\n\r\n\u2514\u2500\u2500\u2500.devcontainer.json\r\n\r\nOr\r\n\r\n\u2514\u2500\u2500\u2500.devcontainer\r\n        \u251c\u2500\u2500\u2500devcontainer.json\r\n\r\nor to support multiple dev containers (where &lt;folder&gt; is one level deep)\r\n\r\n\u2514\u2500\u2500\u2500.devcontainer\r\n        \u2514\u2500\u2500\u2500&lt;folder&gt;\r\n                \u2514\u2500\u2500\u2500devcontainer.json<\/code><\/pre>\n<p>The <code>devcontainer.json<\/code> file contains several <a href=\"https:\/\/containers.dev\/implementors\/json_reference\">configuration options<\/a>, but This post focuses on the minimum required metadata to create a fully featured development environment:<\/p>\n<ul>\n<li><code>name<\/code>: the container name (could be your project or service name)<\/li>\n<li><code>image<\/code>: the container image to use, list of available images can be found <a href=\"https:\/\/mcr.microsoft.com\/en-us\/catalog?search=devcontainers\">here<\/a><\/li>\n<li><code>features<\/code>: features are self-contained, shareable units of installation code and development container configuration<\/li>\n<li><code>Lifecycle Scripts<\/code>: different commands to be run at different points in the container\u2019s lifecycle, like <code>initializeCommand<\/code>, <code>postCreateCommand<\/code>, <code>postStartCommand<\/code>,<code>postAttachCommand<\/code>, and <a href=\"https:\/\/containers.dev\/implementors\/json_reference\/#lifecycle-scripts\">more<\/a><\/li>\n<li><code>customizations<\/code>: IDE specific properties, defined in <a href=\"https:\/\/containers.dev\/supporting\">supporting tools<\/a><\/li>\n<\/ul>\n<h3>1. Selecting the dev container image<\/h3>\n<p>Based on your project technology stack, you need to select the container image that contains all the tools and dependencies needed to run your development environment.<\/p>\n<ul>\n<li><a href=\"https:\/\/mcr.microsoft.com\/en-us\/catalog?search=devcontainers\">List of available images<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/devcontainers\/images?tab=readme-ov-file\">Images source code<\/a><\/li>\n<li><a href=\"https:\/\/mcr.microsoft.com\/en-us\/product\/devcontainers\/base\/tags\">Base Development Container Images<\/a><\/li>\n<\/ul>\n<p>The following example shows how to configure your dev container to use the Java 17 on Debian OS container image.<\/p>\n<pre><code class=\"language-json\">{\r\n  \"name\": \"Java 17\",\r\n  \"image\": \"mcr.microsoft.com\/devcontainers\/java:17-bullseye\",\r\n}<\/code><\/pre>\n<p>or using Dockerfile<\/p>\n<pre><code class=\"language-json\">{\r\n  \"name\": \"Java 17\",\r\n  \"build\": {\r\n    \"dockerfile\": \"\/path\/to\/your\/Dockerfile\",\r\n  }\r\n}<\/code><\/pre>\n<h3>2. Installing tools and dependencies using features<\/h3>\n<p>Dev containers features are self-contained shareable units of installation code and development container configurations. It can be used to install CLIs, tools, and dependencies. Please check the <a href=\"https:\/\/containers.dev\/features\">list of available features<\/a>. The following example shows how to install Java, Maven, Azure-CLI and Docker using dev container&#8217;s features:<\/p>\n<pre><code class=\"language-json\">{\r\n  \"name\": \"Java 17\",\r\n  \"image\": \"mcr.microsoft.com\/devcontainers\/base:ubuntu\",\r\n  \"features\": {\r\n    \"ghcr.io\/devcontainers\/features\/java:1\": {\r\n      \"version\": \"17\",\r\n      \"installMaven\": \"true\",\r\n      \"installGradle\": \"false\"\r\n    },\r\n    \"ghcr.io\/devcontainers\/features\/azure-cli:1\":{}, \/\/ Install Azure CLI\r\n    \"ghcr.io\/devcontainers\/features\/docker-in-docker:2\":{}, \/\/ install docker\r\n }\r\n}<\/code><\/pre>\n<h3>3. Executing commands using lifecycle scripts<\/h3>\n<p>In case you need to execute commands or add more tools that are not supported by features, you can use lifecycle scripts to execute these commands at different points in the container\u2019s lifecycle. The following example shows how to install Git using lifecycle commands <code>postCreateCommand<\/code>:<\/p>\n<pre><code class=\"language-json\">{\r\n  \"postCreateCommand\": \"apt-get update &amp;&amp; apt-get install -y git\"\r\n}<\/code><\/pre>\n<p>or using script file<\/p>\n<pre><code class=\"language-json\">{\r\n  \"postCreateCommand\": \"bash .devcontainer\/postCreateCommand.sh\"\r\n}<\/code><\/pre>\n<h3>4. IDE specific properties<\/h3>\n<p>IDE-specific properties can be used to customize the source code editor. A reference to these customizations can be found <a href=\"https:\/\/containers.dev\/supporting\">here<\/a>. The following example shows how to install VS Code extensions:<\/p>\n<pre><code class=\"language-json\">{\r\n  \"customizations\": {\r\n      \/\/ Configure properties specific to VScode\r\n      \"vscode\": {\r\n        \"settings\": {},\r\n        \"extensions\":[\r\n          \"ms-azuretools.azure-dev\",\r\n          \"ms-azuretools.vscode-docker\",\r\n          \"ms-vscode.makefile-tools\",\r\n          \"ms-kubernetes-tools.vscode-kubernetes-tools\",\r\n          \"ms-kubernetes-tools.vscode-aks-tools\",\r\n          \"vscjava.vscode-java-pack\",\r\n          \"vscjava.vscode-gradle\",\r\n          \"github.vscode-github-actions\",\r\n          \"github.vscode-pull-request-github\",\r\n          \"k6.k6\"\r\n        ]\r\n      },\r\n      \/\/ Configure properties specific to Codespaces.\r\n    \"codespaces\": {\r\n        \"openFiles\": [ \/\/ Customize which files are initially opened when the codespace is created:\r\n          \"README\"\r\n          \"src\/index.js\"\r\n        ]\r\n    }\r\n  }\r\n}<\/code><\/pre>\n<h3>5. Environment variables<\/h3>\n<p>The environment variables are critical part of the development environment, it can be used to store secrets, configuration values, etc. These variables can be set inside the dev container using different ways:<\/p>\n<pre><code class=\"language-json\">\/\/  Using the `runArgs` property\r\n{\r\n  \"runArgs\": [\"--env-file\",\".env\"],\r\n}<\/code><\/pre>\n<pre><code class=\"language-json\"> \/\/ Using the `containerEnv` property\r\n{\r\n  \"containerEnv\": { \r\n    \"MY_VARIABLE\": \"${localEnv:MY_VARIABLE}\" \r\n   }\r\n}<\/code><\/pre>\n<h3>6. Running the dev container<\/h3>\n<p>After creating the <code>devcontainer.json<\/code> file, and setting up all the configurations required by your environment , you can run the dev container using the following:<\/p>\n<ul>\n<li>Open the command palette <code>Ctrl+Shift+P<\/code><\/li>\n<li>Select <code>Dev Containers: Reopen in Container<\/code><\/li>\n<\/ul>\n<h3>7. More and beyond<\/h3>\n<p>More documentation and examples can be found <a href=\"https:\/\/containers.dev\/implementors\/json_reference\/\">here<\/a>, In addition, you can find a very helpful <a href=\"https:\/\/www.youtube.com\/playlist?list=PLj6YeMhvp2S6GjVyDHTPp8tLOR0xLGLYb\"><code>Dev Containers How To<\/code><\/a> series on VS code team YouTube channel.<\/p>\n<p>Also, the dev container can improve the outer development loop by integrating it with the CI\/CD pipeline to build, test, and deploy your application. It is now supported by the major Devops providers like <a href=\"https:\/\/github.com\/marketplace\/actions\/dev-container-build-and-run-action\">GitHub Actions<\/a> and <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=devcontainers.ci\">Azure Devops<\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this post we explored the concept of development containers and how they can help us to solve the challenges we face when setting up our development environment or onboarding a new team member. We also explored how to create a dev container using VS Code.<\/p>\n<h2>References<\/h2>\n<ul>\n<li><a href=\"https:\/\/containers.dev\/\">Dev Containers Specification<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/devcontainers\">Dev Container Repository<\/a><\/li>\n<li><a href=\"https:\/\/containers.dev\/templates\">Dev Container Templates<\/a><\/li>\n<li><a href=\"https:\/\/code.visualstudio.com\/docs\/devcontainers\/containers\">Visual Studio Code Dev Containers<\/a><\/li>\n<li><a href=\"https:\/\/www.jetbrains.com\/help\/idea\/using-docker-as-a-remote-interpreter.html\">IntelliJ Dev Containers<\/a><\/li>\n<li><a href=\"https:\/\/microsoft.github.io\/code-with-engineering-playbook\/developer-experience\/devcontainers\/\">ISE Dev Containers: Getting Started<\/a><\/li>\n<li><a href=\"https:\/\/www.youtube.com\/playlist?list=PLj6YeMhvp2S6GjVyDHTPp8tLOR0xLGLYb\">Dev Containers How To<\/a><\/li>\n<li><a href=\"https:\/\/www.youtube.com\/playlist?list=PLj6YeMhvp2S7FFvNDj7ks7ndm0u69Ufrs\">Dev Containers Overview<\/a><\/li>\n<\/ul>\n<h2>Attribution<\/h2>\n<p>The cover image is referenced from <a href=\"https:\/\/containers.dev\/img\/dev-container-stages.png\">Dev Containers Overview<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post talks about the concept of development containers and how it can help us to solve the challenges we face when setting up our development environment or onboarding a new team member.<\/p>\n","protected":false},"author":119131,"featured_media":15407,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,3451],"tags":[3390,3515,3514,3516,219,3384],"class_list":["post-15406","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cse","category-ise","tag-container","tag-dev","tag-development","tag-intellij","tag-java","tag-vscode"],"acf":[],"blog_post_summary":"<p>This post talks about the concept of development containers and how it can help us to solve the challenges we face when setting up our development environment or onboarding a new team member.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/15406","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/119131"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=15406"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/15406\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/15407"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=15406"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=15406"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=15406"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}