Building a docker image with Gitlab CI and .NET Core

Jelle Verheyen
FAUN — Developer Community 🐾
7 min readApr 23, 2019

--

Nowadays, there is a heavy focus on scalability and containerization but also automatisation. As developers, we want things to be setup once and with the click of a button (or sometimes not even), push our application to production and be done with it. The first step to achieving this is being able to create a Docker image from a CI Pipeline.

I thought this would be an interesting topic for me to write about as it’s a rather important step to achieving Continuous Deployment and perhaps not the most straightforward.

Getting started

I’m going to keep it as simple as possible and will try to explain most things as well as I can so there is not much required except:

  • Basic knowledge of docker
  • Basic knowledge of Gitlab CI (see my other post)
  • Have a basic understanding of how .NET Core projects are built

The project structure we will be working with is:

example
└───src
├───example.sln
└───Example.Presentation
├───Controllers
└───...

It’s just a simple webapi project, generated with dotnet new webapi .

However, by default, dotnet new does not generate a .sln file so make sure you create one in the /example/src/ folder as the pipeline we will be creating assumes there is a solution file in that location.

Setting up our pipeline

Our pipeline will basically build the docker image through a Dockerfile and then push it to our Container Registry on Gitlab.
We will make sure we steer clear of using any keys, credentials, tokens, etc. inside our pipeline to prevent them from being stolen.

Let’s add a .gitlab-ci.yml file in our /src directory and get started.

Our image will be docker:stable :

Next, we will have to define a service named docker:dind . DinD stands for “Docker inside Docker”. We need this as our Gitlab Pipeline is basically a docker container, therefore our docker container will have to spawn another docker container. We call this Dockerception. (no clue if anyone actually says that 🤔).

Defining base image and services

So now with that out of the way, we can get into the actual script.

Before doing any jobs I like running the docker info command which might help when you’re debugging the pipeline:

Run docker info before executing the script

Let’s create a build stage which only runs on the master branch.

Only allow the pipeline to run on master branch

So now for the actual building of the image.
Before we run any docker commands, we want to log into our registry.

We want to be able to do this without directly using credentials inside the pipeline, we will make use of Gitlab’s predefined environment variables. Here, we only make use of a fraction of the available variables but you can find a list of all of them here, the names can change sometimes so make sure your gitlab instance is up to date if it’s self-hosted.

Let’s add a docker login command. We have to pass the URL of the Gitlab registry, our username and password.

Login to the docker registry

As you can see; we’re logging into registry.gitlab.com without providing any hardcoded credentials, exactly as we want!

So now.. building and pushing the image to our registry!
It’s rather simple, really. We just have to run docker build and docker push .

We want to push it to the registry specific to our repository in Gitlab. This is located at registry.gitlab.com/{USER}/{REPOSITORY}, of course, there is an environment variable already defined for all this.

Build and push the image

So we run a build with the-t or —-tag flag, basically giving a name to our image. Then we push it to our Gitlab Registry .

Now, we log out and tell the runner to run the build stage.

Finishing up the pipeline

And.. boom! We’re done with setting up our pipeline!
But.. we’re not quite done yet. docker build requires a Dockerfile which defines all the steps required to build the image.

The Dockerfile

A Dockerfile is used by docker to build an image. It has all the steps in it required to build the image, from setting the base image to building the project. It can be a bit hard to get at first but I’ll run through and explain every step as best I can.

Creating the Dockerfile

Simply create a Dockerfile in the same directory as the gitlab-ci.yml and we can get started :)

Base image

The base image we will be using is microsoft/dotnet:2.2-sdk . If you’re reading this a while from now, you might be using a different version of .NET Core, in which case you can find the latest image here.

A base image can be used so you don’t have to manually install all packages (.NET Core SDK in our case). Using images from the Docker Hub is not always the safest as there might be security flaws etc. In our case it’s okay but if you’re going to deploy to production you might want to make sure it’s secure and ready for production or create your own base image.

Temporary Container
This is not our final container, its base image is the dotnet SDK, which is not what we want when we push our project to production. However, we do need the SDK in order to build the project.
Later on, we will create one based on the dotnet runtime. This will become more clear in a bit.

Defining the base image

Copying *.csproj files

We want to restore our packages now, as you know, all this is saved in the .csproj file so we will have to copy each one to our docker image.
In this example there is only 1 project but if you have multiple in your own project, you will have to copy those too.

Below, we first run a WORKDIR command. This simply changes the directory to /source . If the directory doesn’t exist, it’ll create it.

Afterwards, we run COPY ["{Location inside gitlab-ci}", "{New location inside Docker image}"] . This copies our file to the image.

Copying csproj files to the temporary image

Restoring packages

Now that the *.csproj files are copied, we can run a dotnet restore on our solution’s entrypoint. Generally, when running dotnet restore on the presentation layer, packages for all files will be restored.

In a Dockerfile, you can run a command by prefixing it with RUN.

Afterwards, we copy all our source code into our image (to our current directory, the /source folder, remember?). It might look a bit weird but it’s basically doing the same thing as when we copied our .csproj files. COPY {location inside gitlab-ci} {new location inside docker image}

Restore our project’s packages and copy source code to temporary image

Building the project

It’s very simple here, we change our WORKDIR to /source/src . This is because we copied all our files into the /source folder, meaning we have to move into the /src folder to publish the project.

Then, we run a dotnet publish command. The exact command may vary for your specific project but we’re simply publishing it with --configuration Release and the output folder as /publish . Simply stuff ;)

Building our project for release

Creating the final container

Now, our project is published but it’s using an SDK image, which is not what we want. So we will define a new base image, set the WORKDIR to /publish , copy the published files to the new image and set the entrypoint of the project to our Presentation layer.

Create final image, copy published project files and set the entrypoint

Now our Dockerfile is finished and so is the pipeline.
If you push a commit, the pipeline will be executed. Make sure your Registry is enabled as this might not always be enabled by default.

Pipeline passes

Now, when you go to the Container Registry in the sidebar of your gitlab repo, you can see that an image has been created and pushed to the registry.

Container registry shows the image that has been created.

Conclusion

We went through all the steps of setting up a Gitlab CI Pipeline and building a docker image. Not the most difficult thing to do but definitely a very interesting topic.

I hope this post provided you with a good amount of knowledge to start creating and expanding your own pipeline.

The example project can be found: here.

Any questions or feedback is always very much appreciated!

Follow us on Twitter 🐦 and Facebook 👥 and join our Facebook Group 💬.

To join our community Slack 🗣️ and read our weekly Faun topics 🗞️, click here⬇

If this post was helpful, please click the clap 👏 button below a few times to show your support for the author! ⬇

--

--