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.
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.
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.
Very interesting