How do I publish a .NET Core 1.1 application in a Docker container?

With the advent of Microsoft embracing Docker; it’s now possible to release .NET Core apps in Docker containers; and it’s a first class citizen. This means instead of creating custom Docker images, Microsoft has released multiple docker images you can use instead.

The cool thing about these Docker images is that their Dockerfiles are on Github, which is quite amazing if you like to create custom Docker images.  Without more ado, here’s how I set up the project’s Dockerfile, and I created a build.sh file so that I could script this repeatedly.

Dockerfile:

FROM microsoft/dotnet:1.1.0-runtime
ARG source=./src/bin/Release/netcoreapp1.1/publish
WORKDIR /app
COPY $source .
EXPOSE 5000
ENTRYPOINT [“dotnet”, “MyProject.dll”]

Let’s take it line by line:

FROM microsoft/dotnet:1.1.0-runtime says to create a new image Microsoft’s dockerhub against the dotnet repository, against the tag named 1.1.0-runtime.

ARG source=./src/bin/Release/netcoreapp1.1/publish says to create a variable called source that has the path of ./src/bin/Release/netcoreapp1.1/publish (the default publish directory in .NET Core 1.1).  This path is relative to the project.json file.

WORKDIR /app means to create a directory  in the docker container and make it the working directory.

COPY $source . says to copy the files located at the $source to /app, since that directory was previously defined as the working directory.

EXPOSE 5000 tells docker to expose that port in the container so that it’s accessible from the host.

ENTRYPOINT ["dotnet", "MyProject.dll"] says the entrypoint for the container is the command: dotnet MyProject.dll.

This would be the same as:

CMD "dotnet MyProject.dll"

So that’s the docker file, but there are a few other steps to get a running container; first you have to make sure you’re running ASP.NET Core applications against something other than localhost, and then you still have to publish the application, create the docker image, and run the docker container based on that image. I created a build.sh file to do that, but you could just as easily do it with PowerShell:

SERVICE="my-project"
# change directory to location of project.json
pushd ./src 
# run dotnet publish, specify release build
dotnet publish -c Release
# equivalent to cd .. (go back to previous directory)
popd
# Create a docker image tagged with the name of the project:latest
docker build -t "$SERVICE":latest .
# Check to see if this container exists.
CONTAINER=`docker ps --all | grep "$SERVICE"`
# if it doesn't, then just run this.
if [ -z "$CONTAINER" ]; then
  docker run -i -p 8000:5000 --name $SERVICE -t $SERVICE:latest
# if it does exist; nuke it and then run the new one
else
  docker rm $SERVICE
  docker run -i -p 8000:5000 --name $SERVICE -t $SERVICE:latest
fi

My ASP.NET Core directory structure is set up as follows:

MyProjectDirectory 
|
- src
   \
    - project.json
    - //snip..
- tests
- Dockerfile
- build.sh
- build.ps1

This let’s me keep the files I care about (buildwise) as the base of the directory; so that I can have a master bootstrap file call each directory’s build files depending on the environment. You may want to mix these, but this also allows me to keep certain files out side of Visual Studio (I don’t want it to track or care about those files).

Then, all I have to do to build and deploy my ASP.NET Core 1.1 application is to run:

sh build.sh

And it’ll then build, deploy, change the container if needbe, and start the container.

Fix for ASP.NET Core Docker service not being exposed on host

When attempting to Dockerize my ASP.NET Core micro-service, I ran into an interesting issue. We use Docker’s Network feature to create a virtual network for our docker containers; but for some reason I wasn’t able to issue a curl request against the ASP.NET Docker container, it simply returned:

curl: (52) Empty reply from server

Well crap.  Docker was set up correctly; and ASP.NET Core applications listen on port 5000 typically:

Complete Dockerfile:

FROM microsoft/dotnet:1.1.0-runtime
ARG source=./src/bin/Release/netcoreapp1.1/publish
WORKDIR /app
COPY $source .
EXPOSE 5000
ENTRYPOINT ["dotnet", "MyProject.dll"]

ASP.NET Core dotnet run output:

my-project
Hosting environment: Production
Content root path: /app
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.

Hm. It’s listening on localhost (the docker container), and I’ve exposed port 5000, right? Maybe my port settings are wrong when I spin up the container? Let’s check:

docker run -i -p 5000:5000 --name my-project -t my-project:latest

That checks out. The syntax for docker -p is HOST:CONTAINER, and I’ve made them 5000 on both sides just for testing, but I’m still not getting a response.

I found out that by default, ASP.NET Core applications only listen on the loopback interface — that’s why it showed me localhost:5000. That doesn’t allow it to be viewed externally from the host. Sort of a bonehead moment on my part; but there you are. So to fix it, I can tell Kestrel to listen using the UseUrls() method and specify interfaces to listen on:

public static void Main(string[] args)
{
  var host = new WebHostBuilder()
    .UseKestrel()
    .UseUrls("http://*:5000") //Add this line
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseStartup()
    .UseIISIntegration()
    .Build();

   host.Run();
}

Now, it will listen to all network interfaces on the local machine at port 5000, which is exactly what I want.

How do I debug ASP.NET Core 1.1 applications in Visual Studio 2015?

I updated .NET Core 1.0.1 to .NET Core 1.1.0, and am no longer able to ‘Start Debugging’ (F5) from Visual Studio 2015.

When I tried to debug, I’d get the following error in the output console in Visual Studio:

The program '[6316] dotnet.exe' has exited with code -2147450751 (0x80008081).
The program '[4612] iisexpress.exe' has exited with code 0 (0x0).

The latest version of .NET Core is 1.1 (1.1.0 if you’re a package manager type), but Visual Studio 2015 only supports F5 Debugging with .NET Core 1.0.1.

You can still debug .NET Core 1.1.0 Applications in Visual Studio 2015, however. Here’s how:

Open the Package Manager Console (or use cmd.exe).

Type dotnet run from either console.
You should see the following prompt:

netcorerun

Go to the debug menu in Visual Studio and click “Attach to Process” (or use the Ctrl+Alt+P shortcut).

attachtoprocessLook for dotnet.exe, and hold down shift while selecting all the dotnet.exe processes (it’s one of those three; and you can actually select all at the same time).

Click the “Attach” button.

You’ll know you’re debugging because set breakpoints will change from clear interiors to a red interior (indicating the breakpoints have been loaded).

Happy Debugging!

Fix for Cannot find runtime target for Framework .NETCoreApp=v1

I’ve been working on an ASP.NET Core application; and even though I constantly say I’ll never upgrade in a Dev Cycle, I did.

I updated from .NET Core 1.0.1 to .NET Core 1.1.0, and during the upgrade path, I updated the packages in Nuget using Visual Studio, and suddenly, everything stopped working, specifically I’d get the following error when trying to build:

Can not find runtime target for framework '.NETCoreAPP, Version=v1.0' compatible with one of the target runtimes:

It turns out, Nuget modifies the project.json file in Visual Studio in one specific crucial way: It changes what was previously:

"Microsoft.NETCore.App": {
      "version": "1.0.1",
      "type" :  "platform" 
}

To:

"Microsoft.NETCore.App": "1.1.0",

Notice the difference? the former JSON is an object, and Nuget replaces it with a string. To fix the error, simply make that line look like the object it was previously:

"Microsoft.NETCore.App": {
      "version": "1.1.0",
      "type" :  "platform" 
}

This error should only happen if you try to upgrade in Visual Studio.

How do I change the name of my ASP.NET Core project?

By default, the project output name when building your .NET Core project is the same name as the directory/folder that contains the project.json (which will be back to *.csproj) as of Visual Studio 2017.  So if you’re like me, and you keep your projects like so:

ProjectName
- src
|
 \
  - project.json
  - ProjectName.xproj
  -  <snip>Lots of *.cs files and folders</snip> 
- tests
- Dockerfile

Then you’re going to run into a problem because the name of your output file will be src.dll, since the containing directory is src. Not what you want.

To fix this, you can change a setting in the project.json (or the ProjectName.csproj file):

For project.json, under the buildOptions object:

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true,
    "outputName":  "MyProjectName"       //add this 
  },

Add the outputName property, and your string value will be the name you want to be emitted for your DLLs and settings.

For ProjectName.csproj in Visual Studio 2017, it’s the same property:

  <OutputName>MyProjectName</OutputName>

This will allow you to name the project whatever you want, and not be dependent upon the convention of the folder name as project name.

Five Reasons why You should Join a Startup (as an early employee)

Last week I went through Five Reasons Why I left the Startup Life; and I received a lot of good feedback on the post.  Some of the feedback I received was that that post seemed negative, and looking back, of course it does (after all, it’s about why I left the startup life). But it doesn’t tell the whole story.  While there are reasons why startups have more difficulties than other more established businesses, that isn’t to say you shouldn’t join a startup. In fact, I think if you have the risk tolerance, you should join a startup at least once. Here’s why:

You will learn far more in a startup than a corporate job.  Since there is generally no support structure in a startup, you learn a lot very quickly.  I had never programmed firmware before joining a startup, and my C would be best described as “non-existent” (I don’t consider a college course more than 14 years prior to be useful).  Not only did I have to learn C, but I also had to learn how to ship firmware for a hardware product.  That would never have happened in a corporate job. No one would have said, “Sure, let’s have this person who’s never shipped firmware before write our firmware.”  In a startup, you often don’t have any other choice than to just do it.

You will shake your fear of shipping. There is a vast gulf between developers who can ship software and developers who can not.  In a corporate job, it’s really easy to make changes to the software without moving the shipping needle at all.  In a startup, if you don’t write software that directly contributes to shipping that software, it just won’t ship. It’s scary at first, but once you start shipping, you’ll be addicted to it and wonder how any software team can ever work any other way.

You will have no process to get in your way of shipping.  Once businesses have shipped software, they assume risk for shipping software.  If you don’t have any software, you don’t have any risk to shipping software. Your risk is purely the act of shipping.  That’s part of the reason why startups don’t have any process around shipping. There’s no risk, because nothing is already shipped!  In your corporate job, you know the word “change management”, and it probably makes you shudder. You have meetings upon meetings about change management, and you wish you could just ship software. In a startup, you will.

You control the culture of software and its architecture. While there are other parts of startup culture you can’t control, you will at least have control over the software aspects. Do you want to use AWS? Great. Want to use React? Sure. Want to make sure your software is open source? You can do that.

You will leave work fulfilled, and if you don’t, you only have yourself to blame. In a lot of businesses, there are multiple factors that affect happiness that you don’t control. Too much risk around using your favorite language, too much risk around shipping that neat feature. Every change takes the sign-off of three different people, two of which you don’t interact with when making that change. In a startup, it’s just you. No one can keep you from shipping software that solves your business problem. There’s a study that says people are happier when they exhibit control over their environment.  Startups are as close as you’ll get to being in control of your environment (short of owning your own company).

There are lots of reasons to join a startup that I do justice to here, but I really think you should consider it at least once, and when you do, consider it with eyes wide open.

Five Reasons Why I left Startup Life

Ever since I left the startup life, I’ve been quiet about why.  There are some things I shouldn’t talk about (since I still heart everyone at that startup), but there are some things I can talk about; and I’d like to focus on these, because they aren’t specific to any one startup, and they’re issues you’ll need to face sooner than later if you join as an early stage startup employee.

This critique is focused on early stage startups where you are or will be joining as the first or as employee 1-5.  It’s especially focused on startups that are either still going through their seed round or just finished the seed round.

Startups have no administrative support. Startups are new businesses, and the lack of administration that goes with that.  There are several things you take for-granted in a business, things like insurance, clockwork payroll, T&E systems, and an administrative support system that enables you to focus on doing your job.

None of that exists in a startup. If you’re lucky (and if the startup has the funding), you’ll get some sort of group insurance; but if not, you may be buying it on the individual market (or your company will pay your COBRA, as happened in my case).  If you’re employee #1, they’ll likely just be setting up payroll, and that is fraught with the same problems that any new system has: a lack of use means a lack of habits, which means mistakes. You’ll also probably be the first person to use T&E for your business, which means receipts and spreadsheets.  There are programs that seek to make this easier (ZenPayroll, Gusto, Expensify) but they aren’t free, and they still have the same issues each new system has: integration cost, time, and money. Every time a founder is setting up those systems or working with those systems, they aren’t working on anything else.

Every business person becomes a single point of failure because if they’re out, there aren’t yet processes in place to make sure someone else can handle it.

Being Employee #1 means being the squeaky wheel. If you’re Employee #1 in the company, you are the one that will encounter any cultural, process, or feedback issues, and you’ll encounter them first. Founders have a fight? You’ll know about it (it’s too small not to). Fundraising is hard? You’ll know. What’s the company’s process for paying for your home internet or cellphone that you’ve been using for work? Oh, they said one thing but didn’t follow through? You get to be the one to bring it up. Memorial Day is coming up: Do we have those days off or not? What days do we have off? Do we actually have vacation? How many days? Is your boss missing something fundamental? Are they doing something you wish they’d stop or change?  I hope you can tell them because there isn’t anyone else to, and there’s no one else to talk to about it.   Are they presuming you’ll pay for your trips and then get reimbursed? (Have they forgotten you work for a startup, and by definition make under market value?)

It’s really easy to be micromanaged because everything you do is visible.  In a startup, you have an outsized impact on the outcome. As a software developer, the business impact of what we do is invisible. Set up unit tests? Invisible. Create script to deploy a site? Invisible. However, that time you spent is very much visible. Since you’re obstensibly there to ship the product, those things that make future you’s life easier (like setting up a deploy process or documentation) is de-prioritized.

When I was starting the firmware work, my initial approach was to develop it using TDD. It’s methodical, helps me keep bugs from encroaching, and would enable the architecture to handle our desire to allow user code (by decoupling the architecture).  So I brought up that I was going to spend some time setting up the TDD environment and porting the existing demo code (that I didn’t really understand, to be honest) over to that.

I received a lot of flack from that, including from unexpected places.  Keep in mind, there were only 4 of us total, so to have one person complain about it starts off the second person complaining about it (Yes, GroupThink even hits small groups), and they put the kibosh on that.

As a direct result of that conversation, I was instructed to ‘not worry about setting up the project for TDD’. I spent far more time (easily 3x) fixing bugs incurred by not using TDD than I would have if I had set the project up to be TDD-able from the start.

I need to be clear; this came directly from a combination of the two issues I mentioned above: It’s really easy for a Founder to micromanage one person; and it’s really hard to push back (and yes, I should have pushed back. Not pushing back ended up adversely affecting the project in other ways).

Startups lack reliable funding, especially startups in unproven industries. I won’t say much on this except to say that there was never a moment when we had a year of runway left when I was at the startup; and the number was often much lower than that. This was due to a combination of factors, but the two most prevalent were: 1) we were doing something new and 2) hardware startups are… different. Personally, if Hardware startups have “never been easier” (according to Ben’s closing quote), then I’d hate to have see them before now.

Your life will be filled with constant stress and there’s no one support structure inside your startup to combat that. About two weeks after I left the startup, I visited relatives, and they remarked immediately how less ragged I looked.  I hadn’t noticed, but thinking back to that time; my days were like so:

  1. Get up at 6am. check email, make sure site isn’t down. Get the kids out the door.
  2. Take a shower. Check email.
  3. Get in front of computer around 7am.
  4. Work on development (Firmware, app, website, whatever).
  5. Encounter bug. No one to bounce ideas off of.
  6. Thrash. Sometimes for minutes, sometimes for hours.
  7. Have standup at 10. Explain there’s a bug and I don’t know how long it will take to fix.
  8. Endure sighs from teammates (none of whom either have the time to help, or could really help on this)
  9. go back to figuring out the bug.
  10. Ask Hardware Lead to sit on a hangout to figure out the bug (with no other developers, this was as good as it got)
  11. Figure out bug by 3 or so. Eat lunch.
  12. Work until around 6pm. Eat dinner.
  13. Work again from 8-10, or until bug was fixed / measurable progress was made.
  14. Go to bed.
  15. Rinse, lather, repeat.

Being the only software developer at a company where most of the visible pieces are software is difficult; but even more difficult is not having another developer to work with to solve problems. The Hardware lead was amazing and helped out a lot (I don’t think we would have shipped had he not been my rubber duck); but it’s hard to replace a classically trained software developer, and there was definitely time and money lost due to that.

I burned out three times when working at a startup, and the first time was after I worked through my vacation. (see those dark commits during the two week period at the end of July/Beginning of August, that was my vacation).

Due to issues like developer health and burnout, I firmly believe there should never be a software developer working alone, and this experience only solidified that. Software Development necessarily means building something new, and the cost lost to thrashing and burnout is far more than the cost of hiring a second software developer. This is especially true if you’re asking the developer to build something in a stack they’ve never touched before (for me, Embedded C).

But, I couldn’t very well complain to my teammates, could I? After all, they had their own full time issues; fundraising, building hardware, growth; what could I say to them? Ultimately, I was sacrificing my life and health in a situation where I was just an employee. There was a mismatch of devotion and position. If you work 12+ hour days for a year and a half, but at the end of the day you’re just an employee with the option to buy stock, you’re not going to feel valued.

If I had to do it all over again, I would have adjusted my performance and expectations to be just an employee and put my family and my health first.  I would still do it; but I would not have sacrificed what I did to do it.  We talk a lot about self-care in this industry (though far less than we probably should), but startups and investors still value the worker be that sacrifices their life and health for the “good of the startup”.

I am fully responsible for all the actions I took. I chose to put myself through this because I fully believed in what I was building and I had a mismatch of expectations, thinking that I was more than just an employee of a company.

So you’ve read all this and you are thinking you want to join a startup, great!  Here are some tips I have:

  1. Clear expectations first. This means getting everything in writing and being explicit about your role in the company.
  2. Set boundaries. These could be personal boundaries, it could be professional boundaries. In my case, I should have (nicely) told the other party to pound sand when they told me not to architect the firmware a certain way. I was ultimately responsible for the time and cost overrun due to that; so I should have taken a firmer stand.
  3. Remember, this is a marathon, not a sprint. You are in control of whether you burn out, so make sure to see the warning signs and act before it’s too late.
  4. Negotiate for a package that makes you feel valued, and stick to what you negotiated. If they promise $X raise after Y milestone, bring it up. These were the expectations you were hired with, and it’s no one else’s responsibility but yours to ensure you’re treated according to your expectations.
  5. Unless you’re a founder, you are just an employee. Your devotion and your passion will be used against you, sometimes by you. If you really believe in a project, that’s a warning sign that you should take a step back and rationally think through it. Ask a friend who isn’t emotionally invested.  There’s a reason why car dealers ask how you feel about a car after you test drive it. If you fall in love with the car, you’re more likely to make a worse deal than if you were dispassionate about it. At the end of the day, no matter how passionate you are about your mission, you’re still an employee. You are at least third in line for priorities; sometimes fourth. (Investors, Company, Customers, and then you). If something causes an upset in the balance, you are fourth on the list of people to worry about for the founders.

Joining a startup can be the most rewarding thing you ever do, just make sure you jump into it with eyes wide open.

Update #2:  Four days after I wrote this, I wrote about Five Reasons Why You Should Join a Startup. It’s the other side of the coin, as it were.
Update #1: Removed the name of the startup. Yes, it’s easy to find; but these lessons aren’t specific to any one startup.