November Happy Hour will be moved to Thursday December 5th.

Mark Hall
Sep 21, 2021
  4554
(3 votes)

Working with docker and containers - .NET 5 Series, Part 3

Introduction to Optimizely .NET 5 Series

Welcome everyone to a series of posts about the upcoming releases of .Net 5 compatible commerce and content clouds.

Getting started with upgrade-assistant

Starting a new project

Working with docker and containers

Introduction

Welcome everyone to the third post in a series of posts about the upcoming releases of Optimizely .Net 5. In this post we will explore creating docker images of Optimizely applications and running them in containers.

Install Tools

When working with docker you will need to install the docker tools for you operating system environment. Please note if you work for a large organization you will need to license the software. If you are using Windows you will want to make sure you have the Windows subsystem for linux installed and enabled since we will be building linux images. Finally, we will be using foundation-mvc-cms for the example so please clone or download zip file.

Creating the .env file

We need to create an .env file for the variables that will be used in the image creation. Please place the file src/Foundation.

sa_password=Episerver123!
database_name=foundation.cms
site_port=44387
web_tag=foundation.mvc.cms
sql_tag=foundation.db

Creating the Docker Compose File

The first thing we need to do is to create the docker-compose.yml file. This file is responsible for defining the images and creating the container instances of the images. This file should be placed on the root of the website in the src/Foundation folder.

version: "3"
services:
  sql:
    build:
      dockerfile: ./docker/Sql.Dockerfile
      context: .
    # comment out below lines if you want to expose sql server port for using with other tool like sql server management studio
    ports:
      - "1549:1433"
    environment:
      SA_PASSWORD: ${sa_password}
      ACCEPT_EULA: "Y"
      DATABASE_NAME: ${database_name}
    image: foundation/db:${sql_tag}
  web:
    build:
      dockerfile: ./docker/Web.Dockerfile
      context: .
    ports:
      - "${site_port}:80"
    environment:
      ConnectionStrings__EPiServerDB: "Data Source=sql;Database=${database_name};User Id=netcoreUser;Password=epi#Server7Local;MultipleActiveResultSets=True;"
      site_port: ${site_port}
      ASPNETCORE_ENVIRONMENT: "Development"
    depends_on:
      - sql
    image: foundation/web:${web_tag}

This file is creating two images, sql and web. We need to create a database server for our application as well as a web server to handle our traffic to the Optimizely application. We our passing in environment variables and docker files which describe how to build the image for the container. We can also forward ports from external application to internal container.

Creating Sql.Dockerfile

Lets create a new file called Sql.Dockerfile in src/foundation/docker.

FROM mcr.microsoft.com/mssql/server:2019-latest AS base

USER root

ENV ACCEPT_EULA=Y
ENV MSSQL_TCP_PORT=1433
EXPOSE 1433

WORKDIR /src
COPY ./docker/build-script/SetupDatabases.sh /docker/SetupDatabases.sh
RUN chmod -R 777 /docker/.

ENTRYPOINT /docker/SetupDatabases.sh & /opt/mssql/bin/sqlservr

This file is configuring the sql server image. We are using a bash script to recreate a new database as part of the container initialization. Since we have a content data file we can import the necessary content as part of the build process.

Create SetupDatabases.sh

Create a new bash script file in src/Foundation/Docker/build-script

#!/bin/bash

export cms_db=foundation.cms
export user=netcoreUser
export password=epi#Server7Local

export sql="/opt/mssql-tools/bin/sqlcmd -S . -U sa -P ${SA_PASSWORD}"
echo @Wait MSSQL server to start
export STATUS=1
i=0

while [[ $STATUS -ne 0 ]] && [[ $i -lt 60 ]]; do
    sleep 5s
	i=$i+1
	$sql -Q "select 1" >> /dev/null
	STATUS=$?
    echo "***Starting MSSQL server..."
done

if [ $STATUS -ne 0 ]; then 
	echo "Error: MSSQL SERVER took more than 3 minute to start up."
	exit 1
fi
echo Dropping databases...
$sql -Q "EXEC msdb.dbo.sp_delete_database_backuphistory N'$cms_db'"
$sql -Q "if db_id('$cms_db') is not null ALTER DATABASE [$cms_db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
$sql -Q "if db_id('$cms_db') is not null DROP DATABASE [$cms_db]"

echo Dropping user...
$sql -Q "if exists (select loginname from master.dbo.syslogins where name = '$user') EXEC sp_droplogin @loginame='$user'"

echo Creating databases...
$sql -Q "CREATE DATABASE [$cms_db] COLLATE SQL_Latin1_General_CP1_CI_AS"

echo Creating user...
$sql -Q "EXEC sp_addlogin @loginame='$user', @passwd='$password', @defdb='$cms_db'"
$sql -d $cms_db -Q "EXEC sp_adduser @loginame='$user'"
$sql -d $cms_db -Q "EXEC sp_addrolemember N'db_owner', N'$user'"

echo Done creating databases.

This script waits for the sql server instance to be active, then tries to drop the database if it exists. Next, it will create the database and database user

Creating the Web.Dockerfile

Lets create a new file called Web.Dockerfile in src/foundation/docker.


FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
WORKDIR /app
EXPOSE 8000

FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
WORKDIR /src
COPY . .
RUN dotnet restore "Foundation.csproj" --configfile ./Nuget.config

RUN dotnet build "Foundation.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Foundation.csproj" -c Release -o /app/publish
COPY ./docker/build-script/wait_sqlserver_start_and_attachdb.sh /app/publish/wait_sqlserver_start_and_attachdb.sh
COPY ./App_Data/foundation.episerverdata /app/publish/App_Data/foundation.episerverdata

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
#wait sql server container start and attach alloy database then start web
ENTRYPOINT ./wait_sqlserver_start_and_attachdb.sh

This file is configuring the web server image. We are using a bash script to wait two minutes for the database to initialize before running the Optimizely application.

Create wait_sqlserver_start_and_attachdb.sh

Create a new bash script file in src/Foundation/Docker/build-script

sleep 120s
    echo "Connection string:" $ConnectionStrings__EPiServerDB
    echo "Site port:" $site_port
dotnet Foundation.dll

Building and running the image

Run the following commands to build the images and run them in containers.


cd src/foundation
docker-compose up

This will take a couple minutes to the first time it is run. After building the image, it will run the containers and allow you to browse the site or connect to the database using SQL Management studio from the host machine.

http://localhost:44387/


Server:127.0.0.1,1549
Username: sa
Password: Sa password from .env file

Using DXP bacpac file

In DXP in the pass portal, you have the option of exporting the database to a bacpac file which you can use to import into the container. Lets update the Sql.Dockerfile and SetupDatabases.sh script to import the bacpac file instead of creating new database

Sql.Dockerfile 
FROM mcr.microsoft.com/mssql/server:2019-latest AS base

USER root

ENV ACCEPT_EULA=Y
ENV MSSQL_TCP_PORT=1433
EXPOSE 1433
RUN apt-get update && apt-get install unzip -y

RUN wget -O sqlpackage.zip https://go.microsoft.com/fwlink/?linkid=2165213 \
    && unzip sqlpackage.zip -d /tmp/sqlpackage \
    && chmod +x /tmp/sqlpackage/sqlpackage

COPY database.bacpac /tmp/db/database.bacpac 

WORKDIR /src
COPY ./docker/build-script/SetupDatabases.sh /docker/SetupDatabases.sh
RUN chmod -R 777 /docker/.

ENTRYPOINT /docker/SetupDatabases.sh & /opt/mssql/bin/sqlservr
SetupDatabases.sh
#!/bin/bash

export cms_db=foundation.cms
export user=netcoreUser
export password=epi#Server7Local

export sql="/opt/mssql-tools/bin/sqlcmd -S . -U sa -P ${SA_PASSWORD}"
export dac="/tmp/sqlpackage/sqlpackage /a:Import /tsn:localhost,1433 /tdn:${DATABASE_NAME} /tu:sa /tp:${SA_PASSWORD} /sf:/tmp/db/database.bacpac"
echo @Wait MSSQL server to start
export STATUS=1
i=0

while [[ $STATUS -ne 0 ]] && [[ $i -lt 60 ]]; do
    sleep 5s
	i=$i+1
	$sql -Q "select 1" >> /dev/null
	STATUS=$?
    echo "***Starting MSSQL server..."
done

if [ $STATUS -ne 0 ]; then 
	echo "Error: MSSQL SERVER took more than 3 minute to start up."
	exit 1
fi
echo Dropping databases...
$sql -Q "EXEC msdb.dbo.sp_delete_database_backuphistory N'$cms_db'"
$sql -Q "if db_id('$cms_db') is not null ALTER DATABASE [$cms_db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
$sql -Q "if db_id('$cms_db') is not null DROP DATABASE [$cms_db]"

echo Dropping user...
$sql -Q "if exists (select loginname from master.dbo.syslogins where name = '$user') EXEC sp_droplogin @loginame='$user'"

echo Creating databases...
$dac
rm /tmp/db/database.bacpac
echo Creating user...
$sql -Q "EXEC sp_addlogin @loginame='$user', @passwd='$password', @defdb='$cms_db'"
$sql -d $cms_db -Q "EXEC sp_adduser @loginame='$user'"
$sql -d $cms_db -Q "EXEC sp_addrolemember N'db_owner', N'$user'"

echo Done creating databases.

Next you will need to download the bacpac file and rename to src/Foundation/database.bac

Run the following commands to build the images and run them in containers.


cd src/foundation
docker-compose up

Conclusion

Today we demonstrated how easy it is to containerize and Optimizely application. We can use these techniques to allow for easy configuration of development environments or for production environments. We can also leverage DXP tools to be able to quickly get a solution running in a container for diagnosing issues.

Sep 21, 2021

Comments

Scott Reed
Scott Reed Sep 21, 2021 08:21 AM

Very interesting

  1. Do you think we'll see a docker image registry from Optimizely with alloy, foundation, quicksilver and such images so we can just pull them down with ease
  2. Do you think we'll see the Deployment API / DXP support docker containers soon?

Please login to comment.
Latest blogs
AsyncHelper can be considered harmful

.NET developers have been in the transition to move from synchronous APIs to asynchronous API. That was boosted a lot by await/async keyword of C#...

Quan Mai | Dec 4, 2024 | Syndicated blog

The search for dictionary key

Recently I helped to chase down a ghost (and you might be surprised to know that I, for most part, spend hours to be a ghostbuster, it could be fun...

Quan Mai | Dec 4, 2024 | Syndicated blog

Shared optimizely cart between non-optimizley front end site

E-commerce ecosystems often demand a seamless shopping experience where users can shop across multiple sites using a single cart. Sharing a cart...

PuneetGarg | Dec 3, 2024

CMS Core 12.22.0 delisted from Nuget feed

We have decided to delist version 12.22.0 of the CMS Core packages from our Nuget feed, following the discovery of a bug that affects rendering of...

Magnus Rahl | Dec 3, 2024

Force Login to Optimizely DXP Environments using an Authorization Filter

When working with sites deployed to the Optimizely DXP, you may want to restrict access to the site in a particular environment to only authenticat...

Chris Sharp | Dec 2, 2024 | Syndicated blog

Video Guides: Image Generation Features in Optimizely

The AI Assistant for Optimizely now integrates seamlessly with Recraft AI, providing advanced image generation capabilities directly within your...

Luc Gosso (MVP) | Dec 1, 2024 | Syndicated blog