Running Umbraco CMS on containers

By Ivan Nikolov

5 min read

How to set up Umbraco CMS using Docker containers

Authors

Containers are getting increasingly popular over the last several years, because of the benefits they bring for developing and deploying applications. A container is an isolated unit of software running on top of an operating system that packages up code and all dependencies of an application so that it can run quickly and reliably in different environments. Containers have the following benefits:

  • Consistent and rapid development environment
  • Increased compatibility and maintainability
  • Continuous deployment and testing
  • Simplicity and faster configurations

To offer insight into what effort goes into a container, I described the steps to set up an Umbraco application using a Docker container. Umbraco is an open-source content management system (CMS), based on Microsoft .Net framework. I wanted to investigate how containers can speed up development environment setup, make the application consistent through environments, and enable faster deployments.

For containerization, I used Docker, which allows you to create images and containers running on Linux, Windows, and MacOS operating systems. Docker containerized software/applications are always the same, regardless of the infrastructure.

How does it work?

Let’s start with the steps to create an Umbraco CMS as a .Net application in a Docker container:

  1. Install Docker Desktop for Windows/Linux/MacOS
  2. Install Umbraco Template and create the project
dotnet new install Umbraco.Templates::14.1.1

dotnet new sln --name "UmbracoOnContainers"
dotnet new umbraco -n "Umbraco.Web" 
dotnet sln add "Umbraco.Web"

dotnet run --project "Umbraco.Web"

At this point, I have a running Umbraco website and completing the standard setup wizard will create also the Umbraco local database.

  1. Create Umbraco database in a container

I have now a local database created, which could be moved to a container and accessed from the website container later on. A new folder called "Umbraco.Database" is created to store the Dockerfile and two additional scripts needed to run the database in a container. I have copied the database files (UmbracoDB.mdf and UmbracoDB_log.ldf) from Umbraco initial setup to the database folder since they will be needed to create the Umbraco database in the container. The folder structure should look like this:

Folder structure of the database

The Dockerfile contains a definition of what steps are needed to create a Docker image for our database:

FROM mcr.microsoft.com/azure-sql-edge:latest

ENV ACCEPT_EULA=Y

USER root
 
RUN mkdir /var/opt/sqlserver
 
RUN chown mssql /var/opt/sqlserver
 
ENV MSSQL_BACKUP_DIR="/var/opt/mssql"
ENV MSSQL_DATA_DIR="/var/opt/mssql/data"
ENV MSSQL_LOG_DIR="/var/opt/mssql/log"

EXPOSE 1433/tcp

# Copy Setup SQL script
COPY setup.sql /
COPY startup.sh /

# Copy the database files to the container
# NOTE : This is not a recommendation for production use
COPY UmbracoDB.mdf /var/opt/sqlserver
COPY UmbracoDB_log.ldf /var/opt/sqlserver

ENTRYPOINT [ "/bin/bash", "startup.sh" ]
CMD [ "/opt/mssql/bin/sqlservr" ]

It creates an SQL Server based on azure-sql-edge image and defines environmental variables to configure the paths to be used for databases. It also configures the ports to be exposed (1433) and copies two scripts into the container. These scripts are used to restore the database from the database files when the database container starts. That way when the website starts, it will already have a database in place and will not restore it (if already exists).

setup.sql
USE [master]
GO

IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'UmbracoDb')
BEGIN
    CREATE DATABASE [UmbracoDb] ON 
    ( FILENAME = N'/var/opt/sqlserver/UmbracoDB.mdf' ),
    ( FILENAME = N'/var/opt/sqlserver/UmbracoDB_log.ldf' )
    FOR ATTACH
END;
GO

USE UmbracoDb;
startup.sh
#!/bin/bash
set -e

if [ "$1" = '/opt/mssql/bin/sqlservr' ]; then
  # If this is the container's first run, initialize the application database
  if [ ! -f /tmp/app-initialized ]; then
    # Initialize the application database asynchronously in a background process
    function initialize_app_database() {
      # Wait a bit for SQL Server to start
      sleep 15s
      # Execute the script to create the DB and the schema in the DB
      # Credentials are used here for demonstration purposes, not suitable for Production use
      /opt/mssql-tools/bin/sqlcmd -S localhost -U UmbracoBlogUser -P VeryStrongPassword -d master -i /setup.sql

      touch /tmp/app-initialized
    }
    initialize_app_database &
  fi
fi

exec "$@"
  1. Create Umbraco website in a container

I have already Umbraco running locally, the next step is to create a container for it by defining a Docker image using the following Dockerfile (located in "Umbraco.Web" folder):

# Use the SDK image to build and publish the website
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["Umbraco.Web.csproj", "."]
RUN dotnet restore "Umbraco.Web.csproj"
COPY . .
RUN dotnet publish "Umbraco.Web.csproj" -c Release -o /app/publish

# Copy published output to the final running image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final 
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "Umbraco.Web.dll"]

The Dockerfile starts with defining the base image, which contains .Net 8 SDK to compile and host the project. There are also instructions to copy the working project to the image, download the dependencies, and compile/publish the output of the project. In the end, it defines an entry point to the binary output of the main project in order to run it.

To be able to connect to the database container, I will need to update Umbraco connection string in "appsettings.Development.json" file as follows:

"ConnectionStrings": {
  "umbracoDbDSN": "Server=umbraco_data;Database=UmbracoDb;User Id=UmbracoBlogUser;Password=VeryStrongPassword;TrustServerCertificate=true",
  "umbracoDbDSN_ProviderName": "Microsoft.Data.SqlClient"
}
  1. Use Docker Compose to build and run the containers

In the previous steps, I have defined two Docker images that need to be built and run the corresponding containers - Umbraco Database and Umbraco Web. Let's see how all the above goes together. I will use Docker Compose to deploy both containers using the same network.
Docker Compose is a tool used for defining and running multi-container Docker applications. It allows you to manage the configuration and orchestration of multiple Docker containers with a simple and declarative YAML file. This file, typically named docker-compose.yml, specifies the services (names of the container), networks, volumes, and environment variables required by each container.

In my case, I created a docker-compose.yml file in the main folder of the project.

Folder structure of the main project
version: '3.8'

services:

  # This service defines Umbraco database container. It exposes a volume for storing SQL data and log files
  # outside of the container, written on the physical development machine
  umbdata_image:
    container_name: umbraco_data
    build:
      context: ./Umbraco.Database
    ports:
     - "1433:1433"
     - "1434:1434"
    volumes:
     - umb_database:/var/opt/mssql
    networks:
      - umbnet

  # This service defines Umbraco web container. It exposes port 80 to external port 5000 and stores media and logs
  # in a volume to be accessable outside the container. Volume for uSync can also be defined
  umbweb_cd_image:
    container_name: umbraco_web
    build:
      context: ./Umbraco.Web
      dockerfile: Dockerfile
    volumes:
      - umb_media:/app/wwwroot/media
      - umb_logs:/app/umbraco/Logs
     #- umb_usync:/app/uSync
    restart: always
    environment:
     - ASPNETCORE_ENVIRONMENT=Development
     - ASPNETCORE_URLS=http://+:80
    # Website is be visible on : http://localhost:5000/
    # Umbraco URL is: http://localhost:5000/umbraco
    ports:
      - "5000:80"
    depends_on:
      - umbdata_image
    networks:
      - umbnet          

volumes:
  umb_logs:
  umb_database:
 
  umb_media:
    driver: local
    driver_opts:
      type: 'none'
      o: 'bind'
      device: c:/Projects/UmbracoOnContainersBlog/Umbraco.Web/wwwroot/media
  

# This defines the network, that the containers will be using
networks:
  umbnet:
    driver: bridge

In the Docker compose file I define two services (containers)

  • umbraco_data - points to the image I defined earlier in Umbraco.Database Dockerfile
  • umbraco_web - points to the image I defined earlier in Umbraco.Web Dockerfile

Between them I create a shared network they will use to communicate with each other. I also create volumes for the database, media, and logs files to have persistent storage (similarly, the same could be done for uSync for example, storing its config files). That would mean that deleting and recreating the containers will not remove the database and media files as they are stored in the physical file system of my PC and not in the container itself.

Now that I have everything described in the docker-compose, it is time to start things up using the following commands:

docker compose build

This command will create the images, described in the docker-compose file. Once completed I can see them in Docker Desktop or by using the “docker images“ command in a terminal.

Docker images:

Docker images
docker compose up -d

This command will run the containers based on the previously created images. It will also create the volumes for the Umbraco database and images files.

Docker containers:

Docker containers

Docker volumes:

Docker volumes

Great, I can browse the Umbraco website on address http://localhost:5000. It uses the Umbraco Web and Database containers and has persistent storage for its database, images, and logs.

What comes next?

I now have the Umbraco CMS application running in containers and setting up a development machine plus local development will be a lot easier than before. This however does not fully deliver all the benefits containers can bring. My next step would be to set up a process for continuous integration and deployment using containers. That involves having an image registry to be able to push new images with tags (versions) and deploy them to target environments. For that, I will use Azure Image Registry and Container Apps and will cover it in the next article.

This process is visualized in the following diagram:

Docker CI/CD

Upcoming events

  • Mastering Event-Driven Design

    PLEASE RSVP SO THAT WE KNOW HOW MUCH FOOD WE WILL NEED Are you and your team struggling with event-driven microservices? Join us for a meetup with Mehmet Akif Tütüncü, a senior software engineer, who has given multiple great talks so far and Allard Buijze founder of CTO and founder of AxonIQ, who built the fundaments of the Axon Framework. RSVP for an evening of learning, delicious food, and the fusion of creativity and tech! 🚀 18:00 – 🚪 Doors open to the public 18:15 – 🍕 Let’s eat 19:00 – 📢 Getting Your Axe On Event Sourcing with Axon Framework 20:00 – 🍹 Small break 20:15 – 📢 Event-Driven Microservices - Beyond the Fairy Tale 21:00 – 🙋‍♀️ drinks 22:00 – 🍻 See you next time? Details: Getting Your Axe On - Event Sourcing with Axon Framework In this presentation, we will explore the basics of event-driven architecture using Axon Framework. We'll start by explaining key concepts such as Event Sourcing and Command Query Responsibility Segregation (CQRS), and how they can improve the scalability and maintainability of modern applications. You will learn what Axon Framework is, how it simplifies implementing these patterns, and see hands-on examples of setting up a project with Axon Framework and Spring Boot. Whether you are new to these concepts or looking to understand them more, this session will provide practical insights and tools to help you build resilient and efficient applications. Event-Driven Microservices - Beyond the Fairy Tale Our applications need to be faster, better, bigger, smarter, and more enjoyable to meet our demanding end-users needs. In recent years, the way we build, run, and operate our software has changed significantly. We use scalable platforms to deploy and manage our applications. Instead of big monolithic deployment applications, we now deploy small, functionally consistent components as microservices. Problem. Solved. Right? Unfortunately, for most of us, microservices, and especially their event-driven variants, do not deliver on the beautiful, fairy-tale-like promises that surround them.In this session, Allard will share a different take on microservices. We will see that not much has changed in how we build software, which is why so many “microservices projects” fail nowadays. What lessons can we learn from concepts like DDD, CQRS, and Event Sourcing to help manage the complexity of our systems? He will also show how message-driven communication allows us to focus on finding the boundaries of functionally cohesive components, which we can evolve into microservices should the need arise.

    | Coven of Wisdom - Utrecht

    Go to page for Mastering Event-Driven Design
  • The Leadership Meetup

    PLEASE RSVP SO THAT WE KNOW HOW MUCH FOOD WE WILL NEED What distinguishes a software developer from a software team lead? As a team leader, you are responsible for people, their performance, and motivation. Your output is the output of your team. Whether you are a front-end or back-end developer, or any other discipline that wants to grow into the role of a tech lead, RSVP for an evening of learning, delicious food, and the fusion of leadership and tech! 🚀 18:00 – 🚪 Doors open to the public 18:15 – 🍕 Let’s eat 19:00 – 📢 First round of Talks 19:45 – 🍹 Small break 20:00 – 📢 Second round of Talks 20:45 – 🙋‍♀️ drinks 21:00 – 🍻 See you next time? First Round of Talks: Pixel Perfect and Perfectly Insane: About That Time My Brain Just Switched Off Remy Parzinski, Design System Lead at Logius Learn from Remy how you can care for yourself because we all need to. Second Round of Talks: Becoming a LeadDev at your client; How to Fail at Large (or How to Do Slightly Better) Arno Koehler Engineering Manager @ iO What are the things that will help you become a lead engineer? Building Team Culture (Tales of trust and positivity) Michel Blankenstein Engineering Manager @ iO & Head of Technology @ Zorggenoot How do you create a culture at your company or team? RSVP now to secure your spot, and let's explore the fascinating world of design systems together!

    | Coven of Wisdom - Amsterdam

    Go to page for The Leadership Meetup
  • Coven of Wisdom - Herentals - Spring `24 edition

    Join us for an exciting web technology meetup where you’ll get a chance to gain valuable insights and knowledge about the latest trends in the field. Don’t miss out on this opportunity to expand your knowledge, network with fellow developers, and discover new and exciting possibilities. And the best part? Food and drinks are on us! Johan Vervloet - Event sourced wiezen; an introduction to Event Sourcing and CQRS Join me on a journey into the world of CQRS and Event Sourcing! Together we will unravel the misteries behind these powerful concepts, by exploring a real-life application: a score app for the 'Wiezen' card game.Using examples straight from the card table, we will delve into the depths of event sourcing and CQRS, comparing them to more traditional approaches that rely on an ORM.We will uncover the signs in your own database that indicate where event sourcing can bring added value. I will also provide you with some tips and pointers, should you decide to embark on your own event sourcing adventure. Filip Van Reeth - WordPress API; "Are you talking to me?" What if the WordPress API could be one of your best friends? What kind of light-hearted or profound requests would it share with you? In this talk, I would like to introduce you to it and ensure that you become best friends so that together you can have many more pleasant conversations (calls). Wanna be friends? Please note that the event or talks will be conducted in Dutch. Want to give a talk? Send us your proposal at meetup.herentals@iodigital.com 18:00 - 19:00: Food/Drinks/Networking 19:00 - 21:00: Talks 21:00 - 22:00: Networking Thursday 30th of May, 18h00 - 22h00 CET iO Campus Herentals, Zavelheide 15, Herentals

    | Coven of Wisdom Herentals

    Go to page for Coven of Wisdom - Herentals - Spring `24 edition

Share