---
title: "How to run Umbraco 9 as a Linux Docker container"
excerpt: "Umbraco 9 has been built on top of .NET 5. As a result, you can now containerize your Umbraco 9 websites in Linux containers. Learn how to containerize Umbraco 9 with Docker."
publishDate: "2021-11-25T00:00:00"
createDate: "2021-11-26T02:45:51.683"
updateDate: "2021-11-28T04:47:31.36"
category: "umbraco"
categoryName: "Umbraco"
tags: ["Umbraco",".NET","ASP.NET",".NET Core","ASP.NET Core","Umbraco 9","Docker","Linux","Ubuntu"]
permalink: "/blog/umbraco/how-to-run-umbraco-9-as-a-linux-docker-container"
canonical: "/blog/umbraco/how-to-run-umbraco-9-as-a-linux-docker-container"
image: {"src":"/media/00ddieqg/umbraco-9-as-a-linux-docker-container-long.png","focalPoint":{"left":0.5,"top":0.5},"crops":[{"alias":"Banner","width":1120,"height":400,"coordinates":null}]}
imageAlt: "Umbraco logo next Docker logo next to title: How to run Umbraco 9 as a Linux Docker container"
imageBackgroundColor: "#fff"
thumbnailImage: {"src":"/media/ql4ck34n/umbraco-9-as-a-linux-docker-container-square.png","focalPoint":{"left":0.5,"top":0.5},"crops":[{"alias":"thumbnail","width":250,"height":250,"coordinates":null}]}
thumbnailImageAlt: "Umbraco logo and Docker logo"
metaTitle: "How to run Umbraco 9 as a Linux Docker container"
metaDescription: "Umbraco 9 has been built on top of .NET 5. As a result, you can now containerize your Umbraco 9 websites in Linux containers. Learn how to containerize Umbraco 9 with Docker."
ogTitle: "How to run Umbraco 9 as a Linux Docker container"
ogDescription: "Umbraco 9 has been built on top of .NET 5. As a result, you can now containerize your Umbraco 9 websites in Linux containers. Learn how to containerize Umbraco 9 with Docker."
ogImage: {"src":"/media/yiwjm3jz/umbraco-9-as-a-linux-docker-container-social.png","focalPoint":{"left":0.5,"top":0.5},"crops":[{"alias":"OpenGraph","width":1200,"height":630,"coordinates":null}]}
ogImageAlt: "Umbraco logo next Docker logo next to title: How to run Umbraco 9 as a Linux Docker container"
relatedPostKeys: ["85d3348456ed42af963205d13d2cc60f","c9258c5828ea403382c17c1db8597df9","b995bb9b72b14f26a0817b2c8bee1a57"]
noIndex: false
noFollow: false
sitemapExclude: false
source: {"nodeId":1575,"contentKey":"7732ae3425af4232bc46a1a9925ec356","sortOrder":116}
---

Umbraco 9 has been built on top of .NET 5 (.NET Core). This means you are not limited to hosting Umbraco on Windows anymore, but you can now also host Umbraco on macOS and many popular Linux distributions. As a result, you can also containerize your Umbraco 9 websites in Linux containers using Docker.

## Prerequisites {#prerequisites}

You will need these items to follow along:


-   .NET 5 SDK ([install](https://dotnet.microsoft.com/download/dotnet/5.0))
-   Docker Engine and CLI
-   A Microsoft SQL Server and database
-   A connection string to the database


This tutorial will be using Ubuntu with the bash shell, but you should be able to follow along on Windows with minor modifications.

Umbraco uses Microsoft SQL server as its database. There are many ways to create a Microsoft SQL Server. You could host it on your machine, run it in a container, or use a cloud offering like Azure SQL. This tutorial will not go over how to set up a Microsoft SQL server or how to create a connection string.

## Creating a new Umbraco 9 website {#creating-a-new-umbraco-9-website}

Create a new directory to host your new Umbraco 9 source code in and move into the new directory:

<pre style="color:#000000;background:#ffffff;">mkdir Umbraco9Docker&#10;<span style="color:#7f0055; font-weight:bold; ">cd</span> Umbraco9Docker&#10;</pre>

Run the following command to install the Umbraco 9 templates into the .NET SDK:

<pre style="color:#000000;background:#ffffff;">dotnet new -i Umbraco<span style="color:#7f0055; font-weight:bold; ">.</span>Templates::*&#10;</pre>

Run this command to create a new Umbraco 9 website:

<pre style="color:#000000;background:#ffffff;">dotnet new umbraco&#10;</pre>

If you wanted to, you could build and run this new website directly without using a container using the `dotnet run` command.

## Building a Docker image for your Umbraco 9 site {#building-a-docker-image-for-your-umbraco-9-site}




You have to create a <em>Dockerfile</em> to provide instructions to Docker on how to build the container image. Create a file called <em>Dockerfile </em>and paste in the following content:




<pre style="color:#000000;background:#ffffff;">FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build&#10;WORKDIR /src&#10;COPY [<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Umbraco9Docker.csproj</span><span style="color:#2a00ff; ">"</span>, <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">./</span><span style="color:#2a00ff; ">"</span>]&#10;RUN dotnet restore <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Umbraco9Docker.csproj</span><span style="color:#2a00ff; ">"</span>&#10;COPY . .&#10;WORKDIR <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">/src/.</span><span style="color:#2a00ff; ">"</span>&#10;RUN dotnet publish <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Umbraco9Docker.csproj</span><span style="color:#2a00ff; ">"</span> -c Release -o /app/publish&#10;&#10;FROM mcr.microsoft.com/dotnet/aspnet:5.0&#10;WORKDIR /app&#10;EXPOSE 80&#10;ENV ASPNETCORE_URLS=<span style="color:#3f3fbf; ">http://+:80</span>&#10;COPY --from=build /app/publish .&#10;&#10;ENTRYPOINT [<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">dotnet</span><span style="color:#2a00ff; ">"</span>, <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Umbraco9Docker.dll</span><span style="color:#2a00ff; ">"</span>]&#10;</pre>




This <em>Dockerfile </em>will do the following:





- Copy the <em>Umbraco9Docker.csproj </em>to the <em>src </em>folder.
- Restore the NuGet packages specified in <em>Umbraco9Docker.csproj</em>. Docker creates a layer after every step in the <em>Dockerfile</em>. By only copying the <em>Umbraco9Docker.csproj</em> file and then doing a restore, these two layers will only need to be recreated if the <em>Umbraco9Docker.csproj</em> changes. This speeds up the Docker build significantly if no change has been made to the <em>Umbraco9Docker.csproj</em> file.
- Copy all the source code to the <em>src</em> folder.
- Publish the .NET project in release mode and put the output at <em>/app/publish</em>.
- Expose port 80 so the port can be mapped on the container host.
- Set an environment variable with key 'ASPNETCORE_URLS' and value 'http://+:80'.<br>This environment variable will instruct ASP.NET Core to host the application using HTTP on port 80. The environment variable can be overwritten by the container host.
- Copy the publish output from the build image to the final image.
- Configure <code>dotnet Umbraco9Docker.dll</code> as the entry point. When the image is run, the entry point will be executed inside the container.




<p class="panel panel--info">Note: You can also expose port 443, but setting up HTTPS in Docker containers is a blog post of its own. Docker containers commonly expose port 80 and a reverse proxy/load balancer will decrypt/encrypt traffic for HTTPS.</p>

Execute the following command to build your container image:

<pre style="color:#000000;background:#ffffff;">docker build -t umbraco9-image:latest <span style="color:#7f0055; font-weight:bold; ">.</span>&#10;</pre>

This will build your container image and tag the image as 'umbraco9-image:latest'.<br>By tagging your image, you can run it by its tag later on.

## Running the Umbraco 9 Docker image {#running-the-umbraco-9-docker-image}

Execute the following command to run your image:

<pre style="color:#000000;background:#ffffff;">docker run --rm -t -p 80:80 umbraco9-image:latest&#10;</pre>

Here's what the arguments do:


-   **\--rm**: clean up the container when the container is stopped
-   **\-t**: Allocate a pseudo-tty. This will color code the output among things.
-   **\-p**: Maps port 80 on the host to port 80 in the container
-   **umbraco9-image:latest**: specifies the container image to run





If you browse to [http://localhost:80](http://localhost:80), you should see the website running and Umbraco will prompt you to install the CMS. Unfortunately, you also have to specify the connection string in the wizard. When you enter the connection string, it will be persisted in the *appsettings.json* file in the container, but when you destroy and recreate the container, the changes will be gone and you'll have to go through the wizard again.




### Passing the connection string into the Docker container {#passing-the-connection-string-into-the-docker-container}

Instead, you should pass in the connection string some other way. ASP.NET Core's configuration system parses command-line arguments out of the box, so you can pass in the Umbraco connection string as an argument. First, store the connection string in a variable by running these commands:

<pre style="color:#000000;background:#ffffff;"><span style="color:#7f0055; font-weight:bold; ">echo</span> -n Enter Umbraco database connection string: &#10;<span style="color:#7f0055; font-weight:bold; ">read</span> -s umbracoDbDSN&#10;</pre>

This will prompt you for the connection string and put the entered value into the `umbracoDbDSN` variable.

Next, run this updated `docker run` command:

<pre style="color:#000000;background:#ffffff;">docker run --rm -t -p 80:80 <span style="color:#2a00ff; ">\</span>&#10;    umbraco9-image:latest ConnectionStrings:umbracoDbDSN=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$umbracoDbDSN</span><span style="color:#2a00ff; ">"</span>&#10;</pre>

Umbraco will look for a connection string named `umbracoDbDSN` by default.

<p>Browse to <a href="http://localhost:80">http://localhost:80</a> again. If you haven't installed Umbraco into the database, it'll prompt you for your desired username and password, but not for a connection string. <br>After installing Umbraco on the database, you will be taken to the Umbraco back-office at <a href="http://localhost:80/umbraco">http://localhost:80/umbraco</a>. </p>




If you already had installed Umbraco into the database, you'll be greeted by a standard Umbraco splash page because you're running a blank Umbraco instance without any content or templates. Browse to [http://localhost:80/umbraco](http://localhost:80/umbraco) to see the back-office.


Alternatively, you can also pass in the connection string using an environment variable.<br>Command-line arguments and environment variables get the job done, but there are more secure options to consider. Depending on which container host or container orchestrator you are using, you can pass in secrets as files. Once the secret files are in the container, you can use the [Key-per-file configuration provider](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0#key-per-file-configuration-provider) to parse them into the ASP.NET configuration. This is a common way to pass in secrets to a container that is used by Docker compose, Docker Swarm, Kubernetes, and many other container hosts.

### Change Serilog to log to console {#change-serilog-to-log-to-console}




Unfortunately, you cannot see the logs coming from the website, because out of the box, Serilog isn't configured to log to the console. For you to be able to see logs in docker, it needs to be logged to the console. Update the <em>appsettings.json </em>file by adding the "WriteTo" property to the "Serilog" object with the "Async" configuration below:




<pre style="color:#000000;background:#ffffff;">{&#10;  <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$schema</span><span style="color:#2a00ff; ">"</span>: <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">./umbraco/config/appsettings-schema.json</span><span style="color:#2a00ff; ">"</span>,&#10;  <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Serilog</span><span style="color:#2a00ff; ">"</span>: {&#10;    <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">MinimumLevel</span><span style="color:#2a00ff; ">"</span>: {...},&#10;    <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">WriteTo</span><span style="color:#2a00ff; ">"</span>: [&#10;      {&#10;        <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Name</span><span style="color:#2a00ff; ">"</span>: <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Async</span><span style="color:#2a00ff; ">"</span>,&#10;        <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Args</span><span style="color:#2a00ff; ">"</span>: {&#10;          <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">configure</span><span style="color:#2a00ff; ">"</span>: [&#10;            {&#10;              <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Name</span><span style="color:#2a00ff; ">"</span>: <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Console</span><span style="color:#2a00ff; ">"</span>&#10;            }&#10;          ]&#10;        }&#10;      }&#10;    ]&#10;  },&#10;  <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">ConnectionStrings</span><span style="color:#2a00ff; ">"</span>: {...},&#10;  <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Umbraco</span><span style="color:#2a00ff; ">"</span>: {...}&#10;}&#10;</pre>

Rebuild the container image with the updated source code:

<pre style="color:#000000;background:#ffffff;">docker build -t umbraco9-image:latest <span style="color:#7f0055; font-weight:bold; ">.</span>&#10;</pre>

Run the new image:

<pre style="color:#000000;background:#ffffff;">docker run --rm -t -p 80:80 <span style="color:#2a00ff; ">\</span>&#10;    umbraco9-image:latest ConnectionStrings:umbracoDbDSN=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$umbracoDbDSN</span><span style="color:#2a00ff; ">"</span>&#10;</pre>

You should now see all the logs from your running container.

### Persisting file system using volumes and mounts {#persisting-file-system-using-volumes-and-mounts}



#### Persisting media files {#persisting-media-files}




When you upload files to Umbraco, by default, it will store the files somewhere in the <em>wwwroot/media</em> folder. But when a container is destroyed and recreated, the files uploaded to the <em>wwwroot/media</em> folder are lost. 




<p class="panel panel--info">Note: If you are using a different file provider that stores the files outside of the container, this won't be an issue. </p>

You can use Docker volumes to persist these files even when a container is destroyed and recreated. The first way you could achieve this is by creating a named volume like this:

<pre style="color:#000000;background:#ffffff;">docker run --rm -t -p 80:80 <span style="color:#2a00ff; ">\</span>&#10;     -v media-volume:<span style="color:#3f3fbf; ">/app/wwwroot/media</span> <span style="color:#2a00ff; ">\</span>&#10;    umbraco9-image:latest ConnectionStrings:umbracoDbDSN=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$umbracoDbDSN</span><span style="color:#2a00ff; ">"</span>&#10;</pre>

The command will now also create a volume named 'media-volume' for the */app/wwwroot/media* in the container. If you want to see the files inside this volume from the container host, you can use the following command to get the path to the volume on the host:

<pre style="color:#000000;background:#ffffff;">docker volume inspect media-volume&#10;<span style="color:#3f7f59; "># output:</span>&#10;<span style="color:#3f7f59; "># [</span>&#10;<span style="color:#3f7f59; ">#     {</span>&#10;<span style="color:#3f7f59; ">#         "CreatedAt": "2021-11-23T22:44:48Z",</span>&#10;<span style="color:#3f7f59; ">#         "Driver": "local",</span>&#10;<span style="color:#3f7f59; ">#         "Labels": null,</span>&#10;<span style="color:#3f7f59; ">#         "Mountpoint": "/var/lib/docker/volumes/media-volume/_data",</span>&#10;<span style="color:#3f7f59; ">#         "Name": "media-volume",</span>&#10;<span style="color:#3f7f59; ">#         "Options": null,</span>&#10;<span style="color:#3f7f59; ">#         "Scope": "local"</span>&#10;<span style="color:#3f7f59; ">#     }</span>&#10;<span style="color:#3f7f59; "># ]</span>&#10;</pre>

The Mountpoint property will hold the path to your volume on your container host.

Alternatively, you can use another option to mount a source directory from the host to a destination directory in the container like this:

<pre style="color:#000000;background:#ffffff;">docker run --rm -t -p 80:80 <span style="color:#2a00ff; ">\</span>&#10;    --mount type=<span style="color:#7f0055; font-weight:bold; ">bind</span>,source=<span style="color:#2a00ff; ">"$(pwd)</span><span style="color:#3f3fbf; ">/wwwroot/media</span><span style="color:#2a00ff; ">"</span>,destination=<span style="color:#3f3fbf; ">/app/wwwroot/media</span> <span style="color:#2a00ff; ">\</span>&#10;    umbraco9-image:latest ConnectionStrings:umbracoDbDSN=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$umbracoDbDSN</span><span style="color:#2a00ff; ">"</span>&#10;</pre>




This will mount "$(pwd)/wwwroot/media" on the host where $(pwd) will be replaced with the current directory, and this folder will be mounted to the <em>/app/wwwroot/media </em>folder in the container. The advantage of this approach is that the uploaded files will also be part of your source code. If you don't want the uploaded files to be part of the source code, use the previous approach.





With both options, the files uploaded to the media folder will be persisted when the container is destroyed and recreated. 




#### Persisting log files {#persisting-log-files}

The default log configuration in Umbraco is to log to the *umbraco/Logs* folder. You should turn off [the default UmbracoFile sink](https://our.umbraco.com/Documentation/Reference/V9-Config/Serilog/) if you have your own logging infrastructure. <br>If you do use these log files, you will also need to persist them using volume mounts. You can update the Docker run command like this:

<pre style="color:#000000;background:#ffffff;">docker run --rm -t -p 80:80 <span style="color:#2a00ff; ">\</span>&#10;    --mount type=<span style="color:#7f0055; font-weight:bold; ">bind</span>,source=<span style="color:#2a00ff; ">"$(pwd)</span><span style="color:#3f3fbf; ">/umbraco/Logs</span><span style="color:#2a00ff; ">"</span>,destination=<span style="color:#3f3fbf; ">/app/umbraco/Logs</span> <span style="color:#2a00ff; ">\</span>&#10;    --mount type=<span style="color:#7f0055; font-weight:bold; ">bind</span>,source=<span style="color:#2a00ff; ">"$(pwd)</span><span style="color:#3f3fbf; ">/wwwroot/media</span><span style="color:#2a00ff; ">"</span>,destination=<span style="color:#3f3fbf; ">/app/wwwroot/media</span> <span style="color:#2a00ff; ">\</span>&#10;    umbraco9-image:latest ConnectionStrings:umbracoDbDSN=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$umbracoDbDSN</span><span style="color:#2a00ff; ">"</span>&#10;</pre>




Now you will be able to find the log files both in the container and in the host at *umbraco/Logs*.




#### Persisting Views, Scripts, and Styles {#persisting-views-scripts-and-styles}

Log and media files aren't the only way Umbraco interacts with the file system. In the Umbraco back-office, you can also create, update, and delete view files, script files, and style files.<br>You can simply ignore this if you don't use this feature when running Umbraco inside Docker containers.

<p class="panel panel--info">Note: For the Umbraco websites I develop, I am the only one messing with the source code and I'll update the models, views, CSS, and JavaScript outside of the Docker container. So I don't really interact with these files from the Umbraco back-office and don't set up persistent volumes for them.</p>

If you do need to support these back-office capabilities, you can add more mounts like this:

<pre style="color:#000000;background:#ffffff;">docker run --rm -t -p 80:80 <span style="color:#2a00ff; ">\</span>&#10;    --mount type=<span style="color:#7f0055; font-weight:bold; ">bind</span>,source=<span style="color:#2a00ff; ">"$(pwd)</span><span style="color:#3f3fbf; ">/umbraco/Logs</span><span style="color:#2a00ff; ">"</span>,destination=<span style="color:#3f3fbf; ">/app/umbraco/Logs</span> <span style="color:#2a00ff; ">\</span>&#10;    --mount type=<span style="color:#7f0055; font-weight:bold; ">bind</span>,source=<span style="color:#2a00ff; ">"$(pwd)</span><span style="color:#3f3fbf; ">/wwwroot/media</span><span style="color:#2a00ff; ">"</span>,destination=<span style="color:#3f3fbf; ">/app/wwwroot/media</span> <span style="color:#2a00ff; ">\</span>&#10;    --mount type=<span style="color:#7f0055; font-weight:bold; ">bind</span>,source=<span style="color:#2a00ff; ">"$(pwd)</span><span style="color:#3f3fbf; ">/wwwroot/css</span><span style="color:#2a00ff; ">"</span>,destination=<span style="color:#3f3fbf; ">/app/wwwroot/css</span> <span style="color:#2a00ff; ">\</span>&#10;    --mount type=<span style="color:#7f0055; font-weight:bold; ">bind</span>,source=<span style="color:#2a00ff; ">"$(pwd)</span><span style="color:#3f3fbf; ">/wwwroot/scripts</span><span style="color:#2a00ff; ">"</span>,destination=<span style="color:#3f3fbf; ">/app/wwwroot/scripts</span> <span style="color:#2a00ff; ">\</span>&#10;    --mount type=<span style="color:#7f0055; font-weight:bold; ">bind</span>,source=<span style="color:#2a00ff; ">"$(pwd)</span><span style="color:#3f3fbf; ">/Views</span><span style="color:#2a00ff; ">"</span>,destination=<span style="color:#3f3fbf; ">/app/Views</span> <span style="color:#2a00ff; ">\</span>&#10;    umbraco9-image:latest ConnectionStrings:umbracoDbDSN=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">$umbracoDbDSN</span><span style="color:#2a00ff; ">"</span>&#10;</pre>



#### Persisting Models from ModelBuilder {#persisting-models-from-modelbuilder}




By default, Umbraco 9 will store the models generated from Document Types in-memory without storing them into files. But if you set the <a href="https://our.umbraco.com/documentation/Reference/V9-Config/ModelsBuilderSettings/#models-mode">models mode</a> to `SourceCodeManual` or `SourceCodeAuto`, the model C# files will be created at *umbraco/models*, or whichever directory you configure using the `ModelsDirectory` property. This means you should create another mount just like the others to persist these files.




<p class="panel panel--warning">Warning: When you're running the Docker container locally, using 'bind' mounts, the code will always be in sync, but if you're running the container elsewhere, the code in the container may drift away from your source code. Any changes to the views, CSS, JavaScript, and models should somehow find their way back into your source control system, or remove the capability to edit these source files altogether.</p>



## Next steps {#next-steps}

Now that your Umbraco 9 website is running in a Linux Docker container, you can run it wherever Docker containers are supported, which is pretty much everywhere. If you are an Azure cloud user like me, keep an eye out for more tutorials on how to take this container and host it using Azure App Service, Azure Container Instances, or Azure Container Apps.
