Editor's Note: This post is a follow-on blog post to Setting up Traefik as a Reverse Proxy for ASP.NET Applications.
In this article, we will see how we can Traefik as a reverse proxy for ASP.NET core applications. We will be using Docker containers as our deployment mechanism and use CloudBees Codeship Pro for setting up continuous integration (CI). Let's get started!
We will be using Dot Net Core 2.2 for our project and you can obtain the SDK from here. We will be using Linux as our deployment operating system and assume that it has the Docker engine installed, has access to the public Internet and can run Docker containers.
The demo project along with other files can be found in a Git repository. For the rest of this article, I will assume you have access to the repository both locally and the deployment instance.
Our sample ASP.NET core project is generated using the .NET CLI as follows:
$ dotnet new webapp --no-https --name demo2 ..
--no-https flag is specified so that the Web application doesn't enforce and redirect to HTTPS as we will be using Traefik instead. Hence the secure sockets layer (SSL) termination will happen at Traefik.
The source for the project can be found in the demo2 sub-directory of the above repository. If you have installed the SDK locally, running
dotnet run in the demo2 sub-directory will start kestrel on port 5000:
$ dotnet run info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager User profile is available. Using '/Users/amit/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. Hosting environment: Development Content root path: /Users/amit/work/github.com/amitsaha/traefik-aspnet-demos/demo2 Now listening on: http://localhost:5000 Application started. Press Ctrl+C to shut down.
Next, we will create a
Dockerfile which will create a Docker image of our application:
FROM microsoft/dotnet:2.2-sdk AS build-env WORKDIR /app COPY *.csproj ./ RUN dotnet restore COPY . ./ RUN dotnet publish -c Release -o out # Build runtime image FROM microsoft/dotnet:2.2-aspnetcore-runtime WORKDIR /app COPY --from=build-env /app/out . HEALTHCHECK --interval=5m --timeout=3s \ CMD curl -f http://localhost/ || exit 1 # Assembly name is the same as the project name ENTRYPOINT ["dotnet", "demo2.dll"]
We make use of multi-stage builds to create the final image which only has the built artifacts. Note, how we also specify a
HEALTHCHECK instruction - which will ensure that our service is healthy before any dependent services are spun up (more on this soon).
Instead of building the docker image manually, we will setup continuous integration for our project. There are various free hosted options available out there. For this article, I decided to take CodeShip Pro for a test.
Setting up continuous integration with CodeShip Pro
To setup continuous integration for our repository, we will first have to create a project on CloudBees CodeShip Pro. The free plan should be sufficient for our testing. Once done, we will create two files in our repository -
CloudBees CodeShip Pro adopts a container first approach which means every step that runs - either building a deployment artifact or running your tests must happen inside a container. The
codeship-services.yml file defines the Docker containers that will be used to execute various commands as part of our CI pipeline. It is confusing that these are referred to as services, since they may not be services at all. For example, a container's role could be to just run the tests.
Next, we see a valid
codeship-services.yml file which builds a Docker image for our web application and also specifies another service we will use to perform a basic test on the built image.
The following file specifies that we want the following services to be available during a build:
myapp: build: image: amitsaha/aspnetcore-demo context: demo2 dockerfile: Dockerfile curl: image: pstauffer/curl:latest depends_on: ["myapp"]
The first section of the file configures the myapp service - here we specify that we want to build a Docker image
amitsaha/aspnetcore-demo using the demo2 directory as context and the file
Dockerfile as the Docker file.
Next, we specify the
curl service which will use the
pstauffer/curl:latest Docker image from Docker hub. We also specify that this service has a dependency on the myapp service. This means that myapp service will be created first and then the curl service will be created. The
HEALTHCHECK instruction defined for the myapp image means that the curl service will come up only after the myapp instance is healthy.
codeship-steps.yml file is as follows:
- service: curl command: curl -I http://myapp
The above configuration file states that we want to execute the command
curl -I http://myapp inside the curl service container. Basically this means that a command like
pstauffer/curl:latest curl -I http://myapp will be executed.
To summarize, via the two above files, we are testing:
That our application builds successfully and hence we can build the docker image.
We are able to run the image of our application and make a HTTP GET request successfully.
This is a start - and once we do a push to our Git repository or trigger a build manually, we should see that first a Docker image
amitsaha/aspnetcore-demo (or another specified image name) is built, then a container is created from the image, and once it's healthy, the curl container is created and our command is executed successfully, thus indicating a successful build.
On a successful build, we want to push our docker image to a docker registry. We will add a step to do that to our
codeship-steps.yml file. The complete file will be:
- service: curl command: curl -I http://myapp - service: myapp type: push image_name: amitsaha/aspnetcore-demo encrypted_dockercfg_path: dockercfg.encrypted
By default, the registry is assumed to be Docker Hub. However a number of other registries are supported out of the box. The
dockercfg.encrypted file is an encrypted
dockercfg file generated using the
jet CLI tool and is documented here.
Once we make the above change to our
codeship-steps.yml file along with adding the encrypted docker configuration, the next build will push the built Docker image to the demo Docker hub.
Deploying our application
Now that we have our application Docker image, it's time to deploy our application. We will be working on a Linux instance with Docker installed with connectivity to the public Internet as well as allowing incoming traffic from the public internet over port 80 (for lets encrypt HTTP challenge) and 443 for accessing the web application.
We will be deploying two Docker containers, Traefik and the application itself.
First let's create the Traefik container by running the following command from the demo2 sub-directory:
# repository root $ cd demo2 $ chmod 600 acme.json $ sudo docker run -d -p 80:80 -p 443:443 \ -v $PWD/traefik.toml:/etc/traefik/traefik.toml \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $PWD/acme.json:/acme.json \ traefik
Next, we deploy our web application:
$ image="amitsaha/aspnetcore-demo" $ sudo docker run \ --label "traefik.backend=aspnetcoredemodemo" \ --label "traefik.frontend.rule=Host:aspnetcoredemo.echorand.me" \ --label "traefik.port=80" \ --label "traefik.backend.healthcheck.path=/" \ --label "traefik.backend.healthcheck.interval=5s" \ --label "app=$image" \ -d $image
We use Docker labels to specify various configurations that will specify what traffic is our application interested in and where to send the traffic. The most important configuration is the
traefik.frontend.rule which states that any traffic with the Host header set to
aspnetcoredemo.echorand.me should be sent to this container on port 80.
Note that since we are using LetsEncrypt for SSL certificates, the DNS we provide in the Host above must be valid and publicly resolvable.
Now, from the host, if we run the following command, you will see our web application's home page:
$ curl -k --header 'Host: aspnetcoredemo.echorand.me' https://127.0.0.1/code>
Of course, we can now use a browser to visit the site, https://aspnetcoredemo.echorand.me and we will see our web application.
In this article, we saw how we can deploy a ASP.NET core web application as docker containers on Linux. We used Traefik as a reverse-proxy and setup automatic SSL certificates via LetsEncrypt. Using Traefik also gave us the ability to do zero-downtime deployments and straight forward traffic routing, among a host of other features.
Note: One point worth noting here is that aspnetcoredemo.echorand.me at the time of writing was setup to be valid publicly available DNS record. This is relevant because LetsEncrypt needs the DNS records to be public and resolve correctly to issue SSL certificates. On Amazon AWS, I have created a record set in my Route53 zone and pointed it to the public IP address of my EC2 instance. Since this DNS record will cease to exist at the time you are reading this article, please substitute all references to aspnetcoredemo.echorand.me in this article by a valid public DNS that you own/control.