Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add instructions for developing in a container #420

Merged
merged 20 commits into from
Mar 21, 2018
Merged

Conversation

richlander
Copy link
Member

@richlander richlander commented Mar 12, 2018

Added instructions for establishing a more dynamic container environment.

Copy link
Member

@MichaelSimons MichaelSimons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Very nice addition!


You can [build container images to create your environment](README.md), as described in a Dockerfile. That's straightforward and the common use case with Docker. This document describes a much more iterative and dynamic use of Docker, defining the container environment primarily via the commandline. .NET Core includes a command called `dotnet watch` that can rerun your application or your tests on each code change. This document describes how to use the Docker CLI and `dotnet watch` to develop applications in a container.

This approach relies on [volume mounting](https://docs.docker.com/engine/admin/volumes/volumes/) (that's the `-v` argument in the following commands) to mount the app into the container (without using a Dockerfile). You may need to [Enable shared drives (Windows)](https://docs.docker.com/docker-for-windows/#shared-drives) or [file sharing (macOS)](https://docs.docker.com/docker-for-mac/#file-sharing) first.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - I'm not crazy about the wording mount the app into the container. This doesn't accurately describe what you are mounting to me as the "app" isn't created yet. How about something like app source, source code, or project?

`dotnet watch run` is not working correctly in containers at this time. The instructions are still documented while we work on enabling this scenario.

```console
docker run --rm -p 8000:80 --name aspnetappsample -e ASPNETCORE_URLS=http://+:80 -e DOTNET_USE_POLLING_FILE_WATCHER=1 -v c:\git\dotnet-docker\samples\aspnetapp:c:\app\ microsoft/dotnet:2.0-sdk cmd /c "cd app/aspnetapp && dotnet restore && dotnet watch run"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using the workdir option would be a more standard Docker approach over specifying cd in the docker run command.

`dotnet watch run` is not working correctly in containers at this time. The instructions are still documented while we work on enabling this scenario.

```console
docker run --rm -p 8000:80 --name aspnetappsample -e ASPNETCORE_URLS=http://+:80 -e DOTNET_USE_POLLING_FILE_WATCHER=1 -v c:\git\dotnet-docker\samples\aspnetapp:c:\app\ microsoft/dotnet:2.0-sdk cmd /c "cd app/aspnetapp && dotnet restore && dotnet watch run"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is dotnet restore necessary with SDK > 2.1 for this scenario? It would be nice to get to a point where only a single command instruction is needed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also was thinking about that. The first-order reason (as you touch on) is restore the dotnet watch entrypoint. After 2.1, that's not needed. Still, it's very likely that an application will have dependencies outside of the SDK, which need to restored for this scenario to work.

Copy link
Member

@MichaelSimons MichaelSimons Mar 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I understand the first-order reason for pre-2.1. Outside of that though, I wouldn't think the restore is necessary for >= 2.1 versions because dotnet run will perform an implicit restore.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this command work for a normal ASP.NET Core app now? Given that the aspnetcore shared framework isn't in the dotnet SDK image? Or is that included now? I thought we had moved the DOTNET_USE_POLLING_FILE_WATCHER var into one of the base images, but checking proves that not to be true. Should we do that now? There is no scenario I know of where you can rely on non-polling to have filesystem watcher work with volume mounts. It is this Docker scenario that made us add the switch in the first place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOTNET_USE_POLLING_FILE_WATCHER is not set in our images at the moment. IMO we should add to the 2.1.300 sdk images: aspnet/aspnet-docker#366

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vote for adding DOTNET_USE_POLLING_FILE_WATCHER to the microsoft/dotnet sdk images.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just add ASPNETCORE_URLS as well? It is less obvious than the polling watcher. But if we want people to do this sort of thing then it makes sense.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, let's add both. Seems like ASPNETCORE_URLS should go to the dotnet runtime image.

Given that the aspnetcore shared framework isn't in the dotnet SDK image?

Yes it is. That's why I picked the SDK image.


**Windows** using **Windows containers**

`dotnet watch run` is not working correctly in containers at this time. The instructions are still documented while we work on enabling this scenario.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is not working correctly? Can you link to an issue?

{
message = String.Join(" ",args);
var message = $" {GetMessage(args)}{GetBot()}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Experience isn't great when you just pass in "--with-color". It would be nice to use the defaultMessage in this scenario.


WriteLine("**Environment**");
WriteLine($"Platform: .NET Core");
WriteLine($"OS: {RuntimeInformation.OSDescription}");
WriteLine();
}

public static string GetBot()
private static string GetMessage(string[] message)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent styling of this method is different. Should use 4 space indenting.

@natemcmaster
Copy link
Contributor

We should probably add instructions for how to change the bin/ and obj/ folder. Unless users are developing with Notepad or Vim, they are likely to have issues with volume mounting their source code folder into a Docker container. The SDK writes temp files into obj/ that are machine specific, so either dotnet-watch/dotnet-run will fail inside the container, or the intellisense in VS/VS Code will be broken. Or worse, it will alternate between those two broken states.

To workaround this, I set ENV RunningInsideDocker true and add a Directory.Build.props file to my projects that looks like this:

<Project>
  <PropertyGroup Condition="'$(RunningInsideDocker)' == 'true'">
    <BaseIntermediateOutputPath>$(MSBuildProjectDirectory)/obj/docker/</BaseIntermediateOutputPath>
    <BaseOutputPath>$(MSBuildProjectDirectory)/bin/docker/</BaseOutputPath>
  </PropertyGroup>
  <PropertyGroup Condition="'$(RunningInsideDocker)' != 'true'">
    <BaseIntermediateOutputPath>$(MSBuildProjectDirectory)/obj/host/</BaseIntermediateOutputPath>
    <BaseOutputPath>$(MSBuildProjectDirectory)/bin/host/</BaseOutputPath>
  </PropertyGroup>
</Project>

Unfortunately, this doesn't work with dotnet watch 2.0.0 because dotnet-watch didn't correctly determine the location of the "obj" folder until dotnet-watch 2.1.0-preview1-final.

@richlander
Copy link
Member Author

Any thoughts on the order of the Linux container options? Same q for the unit testing doc. I'd like the first option to be the one that most people are using. We should decide what that is.

@richlander
Copy link
Member Author

@natemcmaster Yes! I was running into this problem (a lot) and going to ask how to do this.

Why is the second case needed?

@natemcmaster
Copy link
Contributor

You could probably do without the second case...haven't tested though. I did this b/c I wanted the output directories to be side-by-side to avoid potential overlap.

In dotnet-watch, we fixed the "obj/" folder issue. There was a hidden workaround in dotnet-watch 2.0.0 for it: https://github.com/aspnet/DotNetTools/blob/617499cd7cc448028303a8c66e54fef6ba8253e9/src/Microsoft.DotNet.Watcher.Tools/CommandLineOptions.cs#L69-L73, but this hidden switch doesn't exist in 2.1.0 since we fixed aspnet/DotNetTools#244

@richlander
Copy link
Member Author

Sorry @natemcmaster . I get the need for the two cases now. I looked at it again.

@richlander
Copy link
Member Author

richlander commented Mar 13, 2018

@natemcmaster I have tried this with 2.0.x and 2.1.4. and get the following error. If I delete d.b.p, then it works. I deleted all obj/bin folders first. dotnet run still works. It is just dotnet watch run that has this issue.

C:\git\dotnet-docker\samples\dotnetapp\dotnetapp>dotnet watch run
watch : Error(s) finding watch items project file 'dotnetapp.csproj'
watch : MSBuild output from target 'GenerateWatchList':
watch :
watch :    Build started 3/12/2018 7:49:04 PM.
watch :         1>Project "C:\git\dotnet-docker\samples\dotnetapp\dotnetapp\dotnetapp.csproj" on node 1 (GenerateWatchList target(s)).
watch :         1>C:\git\dotnet-docker\samples\dotnetapp\dotnetapp\dotnetapp.csproj : error MSB4057: The target "GenerateWatchList" does not exist in the project.
watch :         1>Done Building Project "C:\git\dotnet-docker\samples\dotnetapp\dotnetapp\dotnetapp.csproj" (GenerateWatchList target(s)) -- FAILED.
watch :
watch :    Build FAILED.

@natemcmaster
Copy link
Contributor

Yeah, this is the error I was talking about aspnet/DotNetTools#244. There are a bunch of workarounds, so pick your poison:

  1. Add this to your .csproj file
    <Import Project="obj\$(MSBuildProjectFile).dotnetwatch.g.targets" Condition="Exists('obj\$(MSBuildProjectFile).dotnetwatch.g.targets')" />
  2. dotnet watch --msbuildprojectextensionspath ./obj/docker/ run
  3. Upgrade your DotNetCliToolReference to https://dotnet.myget.org/feed/aspnetcore-dev/package/nuget/Microsoft.DotNet.Watcher.Tools/2.1.0-preview1-27933
  4. Upgrade your SDK to 2.1.300-preview1 and use https://www.nuget.org/packages/dotnet-watch/2.1.0-preview1-final (dotnet install tool -g dotnet-watch --version 2.1.0-preview1-final
  5. Wait for 2.1.300-preview2 which will have dotnet watch bundled and will have the fix for Determine value of MSBuildProjectExtensionsPath  aspnet/DotNetTools#244

@richlander
Copy link
Member Author

Ah, I thought you were saying this was fixed in 2.1.0 and that the hidden switch was removed. Did you mean 2.1.300?

@natemcmaster
Copy link
Contributor

It will be fixed in 2.1.0, but by "2.1.0" I meant dotnet-watch v2.1.0, which ships in SDK v2.1.300. Yeah, that's confusing.

@richlander
Copy link
Member Author

Ah, that explains why I was confused. I saw your earlier directions but didn't follow them because of the confusion. Will try your instructions now.

@dsplaisted
Copy link
Member

@richlander @natemcmaster FYI, we have dotnet/sdk#867 tracking making it easier to redirect the bin and obj folders.

@richlander
Copy link
Member Author

I've given up on making this work with 2.0. I'm going to switch to 2.1 Preview 2+ and assume that the environment variables I need are all there. This will make the experience a lot better.

@richlander
Copy link
Member Author

This change is now done (AFAIK). Waiting on a fix for #426.

@raffaeler
Copy link

I did some experiments using the alpine image (nightly build) running aspnetcore (angular template). I was successful but the 'lesson learned' can be useful in the doc.

  1. the publish layer needs nodejs and nodejs-npm packages which were not available in the alpine image (not sure for the other images). I resolved by running apk add commands

  2. the runtime layer needs nodejs, nodejs-npm and libuv-dev packages

  3. the csproj should set the property PublishWithAspNetCoreTargetManifest to false in order to avoid a lot of missing dependencies when running the app

I can share the multi-stage dockerfile, if you believe it can be helpful.

@richlander
Copy link
Member Author

richlander commented Mar 17, 2018

Yes, please do share your Dockerfile @raffaeler. @glennc is working on something similar, so it would be good to compare notes.

There might be some confusion on the goals of this particular sample. The goal is that no Dockerfile is needed.

@richlander
Copy link
Member Author

@natemcmaster With the latest commit, the samples are finally working for me, as expected! Thanks for your offline feedback.

CTRL-C of the aspnetapp samples doesn't work well for me on macOS. Works on Windows.

@raffaeler
Copy link

First step: create an angular app from a developer command with 2.1 installed
Note: with the following steps, there is no solution, just a csproj in the same folder of the dockerfile

dotnet new angular
npm install
dotnet run

In order to make it work in a container, the following property group must be added to the csproj file

<PropertyGroup>
  <PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
</PropertyGroup>

The following multi-stage dockerfile provides the instruction to build the container with an Alpine Linux distribution and Net Core 2.1 (currently in preview)

FROM microsoft/dotnet-nightly:2.1-sdk-alpine as build
WORKDIR /app

# the first step is to use just the csproj and sln (if any)
# to restore nuget packages
# COPY *.sln .
COPY *.csproj .
RUN dotnet restore

# now copy all the other files and build the app
COPY . .
WORKDIR /app
RUN dotnet build

FROM build AS publish
WORKDIR /app
# the alpine distribution doesn't come with nodejs and npm (separate packages)
RUN apk update
RUN apk add nodejs
RUN apk add nodejs-npm
RUN dotnet publish -c Release -o out

FROM microsoft/dotnet-nightly:2.1-runtime-alpine AS runtime
WORKDIR /app
COPY --from=publish /app/out ./
# nodejs and libuv-dev are required also at runtime
RUN apk update
RUN apk add nodejs
RUN apk add nodejs-npm
#RUN apk add libuv
RUN apk add libuv-dev
#
# if you see the following error ...
# «
# An assembly specified in the application dependencies manifest (basics2.deps.json) was not found:
#   package: 'Microsoft.ApplicationInsights.AspNetCore', version: '2.1.1'
# »
# ... you probably forgot to add add the following to the csproj file
#  <PropertyGroup>
#    <PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
#  </PropertyGroup>
# Doc on this property at:
# https://docs.microsoft.com/en-us/dotnet/core/deploying/runtime-store#aspnet-core-implicit-store
#
EXPOSE 5000
ENTRYPOINT ["dotnet", "basics2.dll"]

Now the container can be built using the docker CLI

docker build -t basics2 . -f dockerfile.test2

If everything worked correctly the docker images command should show the basics2 image

Now the container can be run, specifying the port mapping and the url to listen inside the container itself.

docker run --rm -p 5000:5000 -e "ASPNETCORE_URLS=http://+:5000" basics2

It is also possible to open an interactive bash inside the container, for troubleshooting purposes

docker run --rm -p 5000:5000 -e "ASPNETCORE_URLS=http://+:5000" -it --entrypoint "/bin/ash" basics2

Please feel free to suggest/improve/modify.

@richlander
Copy link
Member Author

Last remaining issue for this PR is #431

Copy link
Member

@MichaelSimons MichaelSimons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good. I have included some suggestions.

{
var defaultMessage = "Hello from .NET Core!";
var bot = GetBot();
var (message, withColor) = GetMessage(args);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it feels like GetMessage should be renamed to ParseArgs. If you disagree, I would suggest that GetMessage should be refactored to contain the logic to default the message when one isn't specified. In other-words GetMessage should encapsulate all of the logic around getting the message to display.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I refactored the code and didn't rename the method. My goal was to add more functionality while keeping the implementation simple. This was the simplest approach I could find.

The [.NET Core Docker Sample](../dotnetapp/README.md) demonstrates more functionality, including unit testing, publishing self-contained applications and using the Alpine base image. The same techniques can be applied to ASP.NET applications.
Looking for other samples:

* [Develop .NET Core Applications in a container](../dotnetapp/dotnet-docker-dev-in-container.md)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is Develop .NET Core Applications in a container called out explicitly but not the other .NET Core app variations (e.g. arm, self-contained, unit testing, etc). To me, it feels like either all of other samples should be listed or all of the other ASP.NET samples plus a single reference to the .NET Core samples readme.

The same comment applies to the mirrored change in the dotnetapp readme.

@@ -0,0 +1,65 @@
# Develop ASP.NET Core Applications in a Container

Docker containers can provide a local .NET Core development environment without having to install anything on your machine. You can also use containers to augment a locally installed development environment with one that matches production. This scenario is useful if you develop on an operating system that is different than production. If you support multiple operating systems, then this approach can be even more useful.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording "without having to install anything" sounds like a line a used car salesman would say...lol. You need to install Docker and it would be nice to have a code editor.


Docker containers can provide a local .NET Core development environment without having to install anything on your machine. You can also use containers to augment a locally installed development environment with one that matches production. This scenario is useful if you develop on an operating system that is different than production. If you support multiple operating systems, then this approach can be even more useful.

You can [build container images to create your environment](README.md), as described in a Dockerfile. That's straightforward and the common use case with Docker. This document describes a much more iterative and dynamic use of Docker, defining the container environment primarily via the commandline. .NET Core includes a command called `dotnet watch` that can rerun your application or your tests on each code change. This document describes how to use the Docker CLI and `dotnet watch` to develop applications in a container.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"container images" - I'm not crazy about this wording. It is confusing because it is mixing two distinctly different Docker concepts. How about just "You can build images"?

"create your environment" - I'm not sure what this is trying to convey. Perhaps describing this in more detail. Is this better?

A common use case of Docker is to containerize an application. You can define the environment necessary to run the application and even build the application itself within a Dockerfile. This document describes...

`dotnet watch run` is not working correctly in containers at this time. The instructions are still documented while we work on enabling this scenario.

```console
docker run --rm --name aspnetappsample -it -p 8000:80 -v c:\git\dotnet-docker\samples\aspnetapp:c:\app\ -w \app\aspnetapp microsoft/dotnet-nightly:2.1-sdk dotnet watch run
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of the args are different for the Windows containers cmds. It would be helpful to use the same order to make it easier for the readers to pick apart the differences across the container environments.


## Test your application in a container while you develop

You can retest your application in a container with every local code change. This works for both console applications and websites. The syntax differs a bit for Windows and Linux containers. You can see this demonstrated in [Develop .NET Core Applications in a Container](../dotnetapp/dotnet-docker-dev-in-container.md).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about removing the This works for both console applications and websites. The syntax differs a bit for Windows and Linux containers. statement here? Let the reference go into the details.

You could provide a direct link to the testing section.

@richlander
Copy link
Member Author

They were conflicts with master. Rebased master content into this branch and pushed -f.

The content is now ready to go.

Copy link
Member

@MichaelSimons MichaelSimons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I just have one comment.

`dotnet watch run` is not working correctly in containers at this time. The instructions are still documented while we work on enabling this scenario.

```console
docker run --name aspnetappsample --rm -it -p 8000:80 -v c:\git\dotnet-docker\samples\aspnetapp:c:\app\ -w \app\aspnetapp microsoft/dotnet-nightly:2.1-sdk dotnet watch run
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a previous comment I made may have been overlooked.

The order of the args are different for the Windows containers cmd. It would be helpful to use the same order to make it easier for the readers to see the differences across the container environments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants