Day 2 - Dev Containers


Introduction
Yesterday, we built and ran a real C# app inside a Dev Container — no fuss, no weird SDK installs, no "it works on my machine."
Today, we’re going to take it a step further by making our container smarter, customizing our dev environment and setting up easy debugging. Our goal is to look like we actually know what we are doing by the end of today.
It’s all easier than you think. Really. Trust me (famous last words).
Let’s get into it.
Step 1: Customize Your Dev Container
You already have a .devcontainer/devcontainer.json
file from yesterday. Let's tweak it a little to make your container feel more like "home."
Here’s a basic customized version:
{
"name": "aspnet-devcontainer",
"build": {
"dockerfile": "Dockerfile"
},
"forwardPorts": [5000, 5001],
"postCreateCommand": "dotnet restore",
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"esbenp.prettier-vscode"
]
}
}
}
This is the full file after customizations:
{
"name": "aspnet-devcontainer",
// Use a prebuilt image from Microsoft for .NET 9
"image": "mcr.microsoft.com/devcontainers/dotnet:1-9.0",
// Add dev container features
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {}
},
// Forward commonly used ports
"forwardPorts": [5000, 5001],
// Automatically restore .NET packages after container creation
"postCreateCommand": "dotnet restore",
// Customize VS Code inside the container
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"esbenp.prettier-vscode"
]
}
},
// (Optional) Run as a normal user instead of root
"remoteUser": "vscode"
}
Let’s look at what we have just done:
forwardPorts: Exposes our app to localhost automatically.
postCreateCommand: Runs
dotnet restore
when our container is ready. No manual step needed.customizations.extensions: Installs useful VS Code extensions inside the container (like C# and Prettier) without you lifting a finger.
No heavy Linux-fu. No Docker black magic. Just a few lines of configuration and your life is easier.
Step 2: Make Sure It Works
We’ve done this before. Every time we change the configuration for the dev container, we need to rebuild the container. So, let’s do it:
Press
Cmd+Shift+P
Type: Dev Containers: Rebuild Container
This will rebuild it based on your new settings.
An issue was encountered verifying workloads.
, do not worry about it. What happens is starting with .NET 6 and beyond Microsoft introduced workloads which are optional SDK features like MAUI, WebAssembly, etc. So, this warning is just SDK trying to validate this optional stuff. In our case, we can either ignore it or suppress it by editing the postCreateCommand
in .devcontainer/devcontainer.json
to "postCreateCommand": "dotnet restore --no-workload"
. (Do not forget to rebuild the container if you do this)When it finishes:
You’ll see your extensions auto-installed.
Your packages are already restored.
Your ports (5000, 5001) are forwarded automatically.
Look at the Extensions tab in VSC. Under the section DEV CONTAINER
, you should see the extensions installed.
Properties/launchSettings.json
will override the forwarded ports here. If you do not specify a port there, then the forwarded ports will be applied.Now that we know our extensions are installed inside the container, let’s look into debugging in the next step.
Step 3: Debugging (Without the Pain)
If you can't set breakpoints, is it even coding? Debugging is basically one real superpower we developers have.
There is hope for dev container after all : VS Code already understands Dev Containers.
Just hit F5, and it will automatically launch your app inside the container with the debugger attached.
You can set breakpoints, step through code, and watch variables.
You don’t need to manually create or edit .vscode/launch.json
unless you want super custom behavior.
(Which you don’t, for now.)
How cool is that?
Step 4: (Optional) Add a Database
If you want to go a little further (completely optional), you can add a docker-compose.yml
to spin up, say, a PostgreSQL container next to your app.
version: '3.8'
services:
app:
build:
context: .
dockerfile: .devcontainer/Dockerfile
ports:
- "5000:5000"
- "5001:5001"
volumes:
- .:/workspace
db:
image: postgres:16
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: devpass
POSTGRES_DB: devdb
ports:
- "5432:5432"
You would then update your devcontainer.json
to use this docker-compose file instead of just the Dockerfile.
.yml
instead of .yaml
? You see, they are actually the same, but with a different extension. It just has historical reasons, such as earlier systems (Unix) only allowing 3 letter extensions. Docker officially uses .yml
in their documentation.Step 5: Real-World Tips
Put
.devcontainer/
under version control (Git) so your teammates (or future you) can spin up the same environment.Use WSL if you're on Windows. (You are already. Good job.)
Rebuild occasionally if things act weird.
Know when NOT to use Dev Containers — for tiny scripts, simple CLI utilities, etc. (Not everything needs a container.)
Quick Recap
We now know how to customize our container and debug it like a native app. At this point, you know enough Linux/Docker to survive and get things done and more importantly you are one of the cool guys.
Seriously though, well done. That wasn’t so bad now, was it?
What’s Next?
You could now follow the optional step and add a database container, or try this with a different language. Just create a Hello World app with Python and try it.
But for the time being? We’re done.
High fives all around. Take care until next time.
Subscribe to my newsletter
Read articles from TJ Gokken directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

TJ Gokken
TJ Gokken
TJ Gokken is an Enterprise AI/ML Integration Engineer with a passion for bridging the gap between technology and practical application. Specializing in .NET frameworks and machine learning, TJ helps software teams operationalize AI to drive innovation and efficiency. With over two decades of experience in programming and technology integration, he is a trusted advisor and thought leader in the AI community