Running Umbraco inside a Docker container

Running Umbraco inside a Docker container

After years of working with Umbraco I was thrilled that Umbraco announced that it was moving the CMS to work on .NET (previously known as ASP.NET Core). As this would allow Umbraco to run in Linux, which in turn would allow me to run Umbraco in Docker.

In this guide I'll be showing how I got everything up and running.

Starting off I made sure my website was upgraded to the latest version of Umbraco (13.7.2 at the time of writing this) back then Umbraco 9. After migrating and making sure al the code was working correctly it was time to set up a Dockerfile in the root of the project. The Dockerfile will use 2 .Net Docker images from Microsoft. mcr.microsoft.com/dotnet/aspnet:8.0 will be the base image where the compiled code will run on. mcr.microsoft.com/dotnet/sdk:8.0 will be the builder image where raw code will be compiled on and published with the resources specified in the project.

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MySite.csproj", "."]
RUN dotnet restore "MySite.csproj"
COPY . .
RUN dotnet build "MySite.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "MySite.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

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

Now that the Dockerfile is ready you can build your Docker image. You do this by running

docker build -t mywebsite .

Test your image by running

docker run -p 8000:8000 --name mysite mywebsite

Docker should now create a container using your image, your site should start now and be reachable on http://localhost:8080/. If you are using a local SQL server for your datase you could run into errors telling you that the database connection has failed. You can remedy this by changing your ConnectionString altering the server part from localhost to host.docker.internal.

Off course this only runs a part of your site in Docker the database is still running outside Docker. But as you probably guessed there is a handy Docker image for that as well. Microsoft has made it's SQL Server software available as Docker images. We'll use one of those to setup a Docker compose file which allows us to handily make both our website and database server available in Docker.

As the base we'll use this Docker compose file(named compose.yaml):

services:
  umbraco:
    image: mywebsite:latest
    container_name: umbraco
entrypoint: ["/wait-for-it.sh", "sql:1433", "--", "dotnet", "MySite.dll"]
    volumes:
      - /path-to-project/media:/app/wwwroot/media:rw
    environment:
      - TZ=Europe/Amsterdam
      - ConnectionStrings__umbracoDbDSN=server=sql,1433;database=mywebsiteDB;user id=sa;password=ChangeMePlease123!;TrustServerCertificate=True
    ports:
      - 8080:8080
    depends_on:
     - sql
    restart: unless-stopped
    
  sql:
    image: mcr.microsoft.com/azure-sql-edge
    container_name: sql
    environment:
      - TZ=Europe/Amsterdam
      - ACCEPT_EULA=y
      - MSSQL_SA_PASSWORD=ChangeMePlease123!
      - mssql_pid=developer
    volumes:
      - /path-to-database-storage/data/data:/var/opt/mssql/data:rw
      - /path-to-database-storage/data/log:/var/opt/mssql/log:rw
      - /path-to-database-storage/data/secrets:/var/opt/mssql/secrets:rw
    restart: unless-stopped

The Docker compose file basically tells docker to start our site and store and retrieve the data for the media folder from our local filesystem allowing us to persist any files uploaded to Umbraco. The same goes for SQL where the databases, logs en secrets are stored on our local filesystem.

This in theory should start both our website and a MS SQL server instance in their respective Docker containers. Hovewer you probably already noticed the wait-for-it.sh file in the entrypoint for the 'umbraco' service. This is a simple shell script which tells the container to hold execution untill SQL is fully up and running.

No you can run the following command to tell docker to start up the configuration defined in you Docker compose file:

docker compose -f compose.yaml up

Umbraco will most likely throw out an error telling you it can't connect to the database. This is to be expected as your SQL container only has its system datases available at this point. We'll need to restore a version of our database in this clean SQL instance the first time, after that the data is persisted to our local filesystem.

To do this we alter our Dockerfile to include some tools for MS SQL so that it can execute commands to the SQL service. You're Dockerfile should be altered to look like this:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
EXPOSE 8080

WORKDIR /tmp

RUN apt-get update
RUN apt-get install -y curl
RUN curl -sSL -O https://packages.microsoft.com/config/debian/$(grep VERSION_ID /etc/os-release | cut -d '"' -f 2 | cut -d '.' -f 1)/packages-microsoft-prod.deb
# Install the package
RUN dpkg -i packages-microsoft-prod.deb
# Delete the file
RUN rm packages-microsoft-prod.deb

RUN apt-get update
RUN ACCEPT_EULA=Y apt-get install -y msodbcsql18 mssql-tools18

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MySite.csproj", "."]
RUN dotnet restore "MySite.csproj"
COPY . .
RUN dotnet build "MySite.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "MySite.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

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

Next we make a backup of our database so that we end up with a backup file mywebsiteDB.bak. To restore this in our Docker contained SQL we'll need to run a SQL commands/queries to the SQL server. The easiest way to do this is to create a sql script (restoreDB.sql) for this. It should look something like this:

USE master
GO;
IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'mywebsiteDB')
BEGIN
    CREATE DATABASE mywebsiteDB;
    RESTORE DATABASE mywebsiteDB;
    FROM DISK = '/misc/mywebsiteDB.bak'
    WITH
    MOVE 'mywebsiteDB' TO '/var/opt/mssql/data/mywebsiteDB.mdf',
    MOVE 'mywebsiteDB_log' TO '/var/opt/mssql/data/mywebsiteDB.ldf',
    REPLACE;
END;
GO;

This sql script checks if the database exists and if that is not the case then attempts to restore it from the backup.

Next up this needs to be executed somehow. I ended up creating a shell script (run.sh) which ran the script and then started Umbraco. Like this:

/opt/mssql-tools18/bin/sqlcmd -C -U $DB_USER -P $DB_PASSWORD -S sql,1433 -i /misc/restoreDB.sql && dotnet MySite.dll

This script pulls a username and password from the containers environment variables so we need to alter our Docker compose file to include those on our Umbraco container. Also we need to make the scripts and backup file available to our containers. For convenience I've saved all the scripts and the backup file to a folder 'path-to-misc'. Alter your Docker compose file as follows:

services:
  umbraco:
    image: mywebsite:latest
    container_name: umbraco
    entrypoint: ["/wait-for-it.sh", "sql:1433", "--", "./run.sh"]
    volumes:
      - /path-to-project/media:/app/wwwroot/media:rw
      - /path-to-misc/run.sh:/app/run.sh
      - /path-to-misc/wait-for-it.sh:/wait-for-it.sh
    environment:
      - TZ=Europe/Amsterdam
      - ConnectionStrings__umbracoDbDSN=server=sql,1433;database=mywebsiteDB;user id=sa;password=ChangeMePlease123!;TrustServerCertificate=True
      - DB_USER=sa
      - DB_PASSWORD=ChangeMePlease123!
    ports:
      - 8080:8080
    depends_on:
     - sql
    restart: unless-stopped
    
  sql:
    image: mcr.microsoft.com/azure-sql-edge
    container_name: sql
    environment:
      - TZ=Europe/Amsterdam
      - ACCEPT_EULA=y
      - MSSQL_SA_PASSWORD=ChangeMePlease123!
      - mssql_pid=developer
    volumes:
      - /path-to-misc:/misc:rw
      - /path-to-database-storage/data/data:/var/opt/mssql/data:rw
      - /path-to-database-storage/data/log:/var/opt/mssql/log:rw
      - /path-todatabase-storage/data/secrets:/var/opt/mssql/secrets:rw
    restart: unless-stopped

You 'path-to-misc' should look like this:

path-to-misc folder

Now run Docker compose again:

docker compose -f compose.yaml up

The Umbraco and SQL containers should start now, after which the Umbraco should instruct the SQL container to restore the database if it doesn't already exists. When this is done Umbraco should start and your site should be fully up and running at: http://localhost:8080/

Some notes:

  • This setup uses the mcr.microsoft.com/azure-sql-edge image which is deprecated. A drop-in replacement for this image is mcr.microsoft.com/mssql/server:2022-latest. The reason for using the azure-sql-edge image is that it will work on a Raspberry Pi 4 (with 8GB memory) which is what I use as a homeserver and testing rig.
  • The examples used show environment variables. This is not best practice in case of connection string and the like which contain credentials. Make sure you obfuscate this in production.