If you haven’t heard about Docker, catch up. Out the gate, the best reasons for me to use Docker is being able to run a production like environment locally and being able to instantly create new test environments without having to go through the tiring dance of manual configuration. So, each test deployment gets a brand new fresh environment and the environment can be thrown away after testing is done. So, Docker delivers on “infrastructure as code” and allows you to save your Docker container configuration in source control and iterate it along with the source code. There are other reasons, but they had me at “create new test environments without having to go through the tiring dance…”
With Windows Server 2016 having Docker support, it’s time to get on board. It has been possible to run ASP.NET/.NET Core on Linux, but I have been waiting to be able to do this on Windows. So, here we go.
- .NET Core 1.1.0 – https://www.microsoft.com/net/download/core
- Docker for Windows 17.03.1-ce – https://docs.docker.com/docker-for-windows/install/#download-docker-for-windows
- Right click the Docker icon in the System Tray, click Settings and select Shared Drives.
- Check the drive to share with Docker.
- Click Apply
- If you installed Docker after installing Visual Studio, restart Visual Studio
- Visual Studio 2017 – https://www.visualstudio.com/
- Enable .NET Core workload
Create New .NET Core Solution
- Create ASP.NET Core Web Project (.NET Core)
- Use the ASP.NET Core 1.1 Template for WebAPI
- Opt to Enable Docker Support
Use Existing .NET Core Solution
- Open an existing .NET Core solution
- Right click on the Web Project in the Solution Explorer and click “Add Docker Support”
When you Enable Docker Support a Docker Compose project is created. The Docker Compose project is Visual Studio’s tooling to manage the creation of Docker containers. It has two YAML files:
- docker-compose.ci.build.yml – configures how the image is built and ran.
- docker-compose.yml – configures the image to be built and ran. It also has nested files that allow you to override the configuration for debug and release (similar to web.config transforms).
Debugging is as simple as debugging traditional web projects. To give a little background on what’s happening behind the scenes, when you F5/debug your Docker containerizes the application (is that a word)
- Visual Studio runs docker compose
- The image defined in the docker-compose.ci.build.yml is downloaded if not in cache
- ASPNETCORE_ENVIRONMENT is set to Development inside the container
- Port 80 is explosed and mapped to a dynamically assigned port for localhost. The Docker host controls the dynamic port. You can see the container port with the Docker CLI command to list containers:
- The application is copied from the build output folder into the continer
- The default browser is launched with the debugger attached to the container.
The application is now running and you can run docker ps to see some of the properties of the running container. The dev image that was built does not contain the application, rather it is mounted from drive we shared with the containers during install. This is to allow you to iterate and make changes while developing without having to to go through the expense of writing the application in the image.
If you make changes to a static file, they are automatically updated without having to recompile. You do have to refresh the browser, so it’s not as lovely as client side development, but better than stopping and starting the container.
If you make changes to a compiled file, hit Ctrl-F5 and the application is compiled and the Kestrel web server is restarted in the container. The container doesn’t have to be rebuilt or stopped, because we don’t have to rebuild the application in the image (hence the reason for the empty dev image).
When you are ready to release the application to Docker Hub or your private hub, you create a production image of the application. When you build in release mode the application is actually copied into the image and unlike dev images, future changes have to be re-imaged.
This was a very pleasing development experience. I had no issues at all (knock on wood) and I was debugging a running .NET Core application in a Docker container locally on Windows 10 in less than 15 minutes (not including install time). I’m still very new at this new tooling and Windows support, so I hope I will get to write more about it as I hit road blocks. There are always road blocks and I’m sure that I will hit them when I answer some of my current questions:
- How to automate and integrate in continuous delivery pipeline
- Container build and publish
- Deploying container and running application to Windows Server 2016 on premise and in Azure
- How to run multiple load balanced containers
- How to monitor containers
- How to deploy more containers to handle increase load or failover
- How to use as base of Microservices architecture
- And the questions keep coming…
I wanted to run a Post Build Event on Release build only. I have never done a conditional event, but found out that it isn’t that difficult. From what I have found so far there are two ways to accomplish this.
If you defined your Post Build Event in the Project Configuration, Build Event screen in Visual Studio, you can add a conditional if statement to define the condition you want the event to run on.
if $(ConfigurationName) == Release ( copy $(TargetPath) $(SolutionDir)\Plugins\$(TargetFileName) )
In this example I compare the $(ConfigurationName) property to the text “Release”. You could replace this with the name of the build configuration you want to run your post build script on. A note on build events, they are translated to batch files then ran so you could do anything that you could do in a bat in your event (this is a big assumption as I haven’t ran every command in a build event yet, but I strongly suspect that its safe to assume most cases will be OK).
If you define your build event directly in the Project file you could
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PostBuildEvent>copy $(TargetPath) $(SolutionDir)\Plugins\$(TargetFileName)</PostBuildEvent> </PropertyGroup>
If you haven’t used Build Events you should check them out as you can bend your build to your will. You can preprocess files before your build, move files after the build, clean directories…basically anything you can do with a batch file you can do in a Build Event, because it is a batch file.
Build Events – http://msdn.microsoft.com/en-us/library/ke5z92ks.aspx
Batch Files – http://technet.microsoft.com/en-us/library/bb490869.aspx
Build Event Macros – http://msdn.microsoft.com/en-us/library/42x5kfw4.aspx