Blog and posts moved to https://adamhathcock.blog/posts/

.NET Core 1.1 building with Docker and Cake

I’m going to attempt to catalog how I’m using Docker to test and build containers that are for deployment into Amazon ECS.

Build Process

  1. Use Dockerfile.build
    • Uses Cake:
      1. dotnet restore
      2. dotnet build
      3. dotnet test
      4. dotnet publish
  2. Save running image to container
  3. Copy publish directory out of container
  4. Use Dockerfile
    • Copy publish directory into image
  5. Push built image to ECS

Driving the build: Cake

I love Cake and have contributed some minor things to it. It does support .NET Core. However, the nuget.exe used to drive some critical things like nuget push does not. push is actually the only command I need that isn’t on .NET Core. So I standardized on requiring Mono for just the build container.

My base Cake file: build.cake

var target = Argument("target", "Default");
var tag = Argument("tag", "cake");

Task("Restore")
  .Does(() =>
{
    DotNetCoreRestore("src/\" \"test/\" \"integrate/");
});

Task("Build")
    .IsDependentOn("Restore")
  .Does(() =>
{
    DotNetCoreBuild("src/**/project.json\" \"test/**/project.json\" \"integrate/**/project.json");
});

Task("Test")
    .IsDependentOn("Build")
  .Does(() =>
{
    var files = GetFiles("test/**/project.json");
    foreach(var file in files)
    {
        DotNetCoreTest(file.ToString());
    }
});

Task("Publish")
    .IsDependentOn("Test")
  .Does(() =>
{
    var settings = new DotNetCorePublishSettings
    {
        Framework = "netcoreapp1.1",
        Configuration = "Release",
        OutputDirectory = "./publish/",
        VersionSuffix = tag
    };
                
    DotNetCorePublish("src/Server", settings);
});

Task("Default")
    .IsDependentOn("Test");

RunTarget(target);

I broke out all the steps as I often run Cake for each step during development. You’ll notice that each dotnet command behaves differently. It’s very annoying.

I have a project structure that usually goes like this:

Other things to notice:

The Build Container: Dockerfile.build

I actually started with following the little HOW-TO from the ASP.NET team from here:

FROM cl0sey/dotnet-mono-docker:1.1-sdk

ARG TAG=docker
ENV TAG ${TAG}

WORKDIR /app
RUN mkdir /publish

COPY . .
RUN ./build.sh -t publish --scriptargs "--tag=${TAG}"

Notice the source image: cl0sey/dotnet-mono-docker:1.1-sdk

Someone was nice enough to already make a Docker image with Mono on top of the base microsoft/dotnet:1.1-sdk-projectjson image. The SDK image is what is needed for using all of the dotnet cli commands that aren’t just running.

Notice:

The Deployment Container: Dockerfile

FROM microsoft/dotnet:1.1.0-runtime

COPY ./publish /app
WORKDIR /app

EXPOSE 5000

ENV ASPNETCORE_ENVIRONMENT beta

ENTRYPOINT ["dotnet", "Server.dll"]

Notice:

Hanging It All Together: CircleCI

I’m using CircleCI as my CI service because it’s free/cheap. Also, it runs Docker and can do Docker inside Docker. The docker commands will work just about anywhere though.

machine:
  services:
    - docker

dependencies:
  override:
    - docker info

test:
  override:
    - docker build -t build-image --build-arg TAG="${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}" -f Dockerfile.build .
    - docker create --name build-cont build-image


deployment:
  beta:
    branch: master
    commands:
    - docker cp build-cont:/app/publish/. publish/
    - docker build -t server-api:latest .
    - docker tag server-api:latest $AWS_ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/server-api:$CIRCLE_BUILD_NUM
    - ./push.sh

Notice:

push.sh to AWS ECS

Finally, I want to save my Docker image.

#!/usr/bin/env bash

configure_aws_cli(){
	aws --version
	aws configure set default.region eu-west-1
	aws configure set default.output json
}

push_ecr_image(){
    eval $(aws ecr get-login --region eu-west-1)
    docker push $AWS_ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/visibility-api:$CIRCLE_BUILD_NUM
}

configure_aws_cli
push_ecr_image

The bash script is copied in part from something else more complicated. You can’t just do the push command from the circle.yaml because of the need to use eval to login to AWS. My AWS push creds are also locked in a CircleCI environment variable that the aws ecr get-login command expects.