Skip to main content

React sample in Docker

Tutorial 2 📦

Source code

main - initial branch
dockerizing - final branch
dockerizing pull request - pull request with code changes


In this second tutorial we describe the code improvements and the steps to follow in order to deploy the React sample application in Docker.



take into account

We recommend reading Docker documentation to get a base understanding of Docker's concepts and principles.

  1. First, you should go through previous tutorials OR clone main branch.
  2. JetBrains Rider or Visual Studio.
  3. Command prompt or Terminal.

In addition, you must download and install the latest version of Docker Desktop.


The required changes in the backend are illustrated in this section. New files must be added, and some files must be modified to run React sample in Docker.


First, the backend should bring up the script for the database. This can be done by creating the class DatabaseUpgrade in the project folder WorkflowApi. This class reads the SQL script from this directory, and it attempts to execute the script in the database. If the execution process presents an exception, the operation will be repeated, a delay is done, and again the procedure for running the script is executed. This is required because the database in the Docker container might start up after the backend application, and during this short time it may be not available, so the exception allows to execute the start script several times to raise the database while it is not successful.

using Microsoft.Data.SqlClient;

namespace WorkflowApi;

public class DatabaseUpgrade
private const int AttemptCount = 100;
private static readonly TimeSpan Delay = TimeSpan.FromSeconds(5);

private readonly string _connectionString;
private readonly string _sqlScript;

private DatabaseUpgrade(string connectionString, string sqlScript)
_connectionString = connectionString;
_sqlScript = sqlScript;

public static async Task WaitForUpgrade(string connectionString)
var sql = await File.ReadAllTextAsync("./Sql/CreatePersistenceObjects.sql");
var instance = new DatabaseUpgrade(connectionString, sql);
await instance.Upgrade();
await Console.Out.WriteLineAsync("The database has been upgraded");

private async Task Upgrade(int attemptNumber = 0)
await Console.Out.WriteLineAsync($"Upgrading database, attempt number: {attemptNumber}");
await UpgradeDatabase();
catch (Exception e)
await Console.Error.WriteLineAsync(e.Message);
if (attemptNumber >= AttemptCount) throw;

await Task.Delay(Delay);
await Upgrade(attemptNumber + 1);

private async Task UpgradeDatabase()
await using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText = _sqlScript;
await command.ExecuteNonQueryAsync();


The WorkflowApiConfiguration was included to define the backend configuration. The Cors and LicenseKey will be set in the docker compose file, so these settings are indicated in the configuration.

namespace WorkflowApi;

public class WorkflowApiConfiguration
public WorkflowApiCorsConfiguration Cors { get; set; }

public string LicenseKey { get; set; }

public class WorkflowApiCorsConfiguration
public List<string> Origins { get; set; }


Next, in the Backend a directory called Sql and the SQL script CreatePersistenceObjects.sql must be added also in WorkflowApi project to create the database objects according the Workflow Engine standard.

Database providers

You can download the MS SQL database script here.

Then, set it in the location: Backend/WorkflowApi/Sql/CreatePersistenceObjects.sql.


Furthermore, the WorkflowApi.csproj must be modified. The script should be published during assembling in the directory with binary and executed files.

<Project Sdk="Microsoft.NET.Sdk.Web">

<ProjectReference Include="..\WorkflowLib\WorkflowLib.csproj" />

<PackageReference Include="WorkflowEngine.NETCore-Core" Version="7.1.3" />
<PackageReference Include="WorkflowEngine.NETCore-ProviderForMSSQL" Version="7.1.3" />

<None Update="Sql\CreatePersistenceObjects.sql">




The Program.cs was also modified. When starting the program, the configuration is set through the configuration file and the script to raise the database is executed. Here, we get apiConfiguration, the Cors settings are changed from AllowAnyOrigin to withOrigins which is taken from the configuration file, the connectionString is gotten by GetConnectionString, the DatabaseUpgrade is launched, and then the connectionString is provided for WorkflowInit. Besides, the LicenseKey is passed to WorkflowRuntime object.

The Cors address will be set from appsettings.json and the script will be run for starting the database. These changes allow to rise the database along with backend and frontend when starting Docker.

using OptimaJet.Workflow.Core.Runtime;  
using WorkflowApi;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

const string rule = "MyCorsRule";
var apiConfiguration = builder.Configuration.Get<WorkflowApiConfiguration>();
if (apiConfiguration?.Cors.Origins.Count > 0)
builder.Services.AddCors(options =>
options.AddPolicy(rule, policy =>

var app = builder.Build();

var connectionString = app.Configuration.GetConnectionString("Default");
if (connectionString is null)
throw new NullReferenceException("Default connection string is not set");
await DatabaseUpgrade.WaitForUpgrade(connectionString);

WorkflowLib.WorkflowInit.ConnectionString = connectionString;

if (!string.IsNullOrEmpty(apiConfiguration?.LicenseKey))


App settings

In the configuration file appsettings.json, we register the Cors, the LicenseKey and the localhost address for the frontend, and also the ConnectionStrings options are set.

  "LicenseKey": "",                                                                               
"Cors": {
"Origins" : ["http://localhost:3000"]
"ConnectionStrings": {
"Default": "Data Source=(local);Initial Catalog=master;User ID=sa;Password=StrongPassword#1"


In addition, the ConnectionString must be replaced with property in WorkflowInit.cs because it is set in the configuration file.

public static class WorkflowInit 
private const string ConnectionString = "Data Source=(local);Initial Catalog=master;User ID=sa;Password=StrongPassword#1";

private static readonly Lazy<WorkflowRuntime> LazyRuntime = new(InitWorkflowRuntime);
private static readonly Lazy<MSSQLProvider> LazyProvider = new(InitMssqlProvider);

public static string ConnectionString { get; set; } = "";
public static WorkflowRuntime Runtime => LazyRuntime.Value;
public static MSSQLProvider Provider => LazyProvider.Value;

All of these are the required changes in the backend application.

Docker Compose

A new directory called docker-files is added in the project root directory with these three files: backend.Dockerfile, frontend.Dockerfile and docker-compose.yml.


The backend.Dockerfile is added for starting the backend application. Here, the backend image is built and placed in a Docker container.

FROM AS build

COPY Backend.sln .
COPY WorkflowApi/WorkflowApi.csproj WorkflowApi/WorkflowApi.csproj
COPY WorkflowLib/WorkflowLib.csproj WorkflowLib/WorkflowLib.csproj
RUN dotnet restore Backend.sln --source

COPY ./ .
RUN dotnet build Backend.sln --configuration Release --output /app

FROM build AS publish
WORKDIR /src/WorkflowApi
RUN dotnet publish WorkflowApi.csproj --configuration Release --output /app

FROM AS base
WORKDIR /app/sample

COPY --from=publish /app ./bin

RUN useradd --user-group --uid 1000 wfe
RUN chown -R wfe:wfe /app

USER wfe

WORKDIR /app/sample/bin
ENTRYPOINT ["dotnet", "WorkflowApi.dll"]


Moreover, the frontend.Dockerfile which runs the frontend through NPM package manager.

FROM node:lts-alpine3.17

RUN mkdir /app

COPY package*.json .
RUN npm install --legacy-peer-deps

ENTRYPOINT ["npm", "run", "start"]


Now, we have docker-compose.yml. We created a YAML file to define the services with Docker compose. It means the list of services (or containers) that we want to run as part of our application.

First, the frontend is defined, it runs on port 3000. The port 3000 inside the container is exposed to port 3000 outside the container.

After that, the backend is started. The environment variables must be specified. The ConnectionStrings settings, the LicenseKey and the Cors Origins as http://localhost:3000 from the frontend. The queries from the frontend must be processed by the backend.

Finally, the database service is also set. The Azure SQL image is used.

name: react-example-docker

- backend
container_name: frontend
hostname: frontend
context: ../frontend
dockerfile: ../docker-files/frontend.Dockerfile
- ../frontend/public:/app/public
- ../frontend/src:/app/src
- 3000:3000
- database
container_name: backend
hostname: backend
context: ../Backend
dockerfile: ../docker-files/backend.Dockerfile
ConnectionStrings__Default: Server=database;Initial Catalog=master;User ID=sa;Password=StrongPassword#1
ASPNETCORE_URLS: http://+:5139
Cors__Origins__0: http://localhost:3000
- 5139:5139
container_name: database
hostname: database
MSSQL_SA_PASSWORD: StrongPassword#1
- 1433:1433
- ./var/mssql-data:/var/opt/mssql/data

Running the application stack

Now, we can start docker-compose.yml file up!

  1. Start up the application stack using the following command. You might add the -d flag to run everything in the background.

    cd docker-files
    docker compose up --build --force-recreate
  2. Once you run the command, you should see this output:

    PS> docker compose up
    [+] Running 4/4
    - Network react-example-docker_default Created 0.9s
    - Container database Created 0.1s
    - Container backend Created 0.4s
    - Container frontend Created 0.1s
    Attaching to backend, database, frontend
    database | Azure SQL Edge will run as non-root by default.
    database | This container is running as user mssql.
    database | To learn more visit
    database | 2023/02/21 11:49:21 [launchpadd] INFO: Extensibility Log Header: <timestamp> <process> <sandboxId> <sessionId> <message>

    Run Docker

  3. Go to http://localhost:3000/ in your preferred browser.

  4. Click on tab Designer to check. If the Designer is loaded properly, it means that everything works ok!

    Open Designer

  5. Upload the scheme provided in the first tutorial.

    Open Designer


That's it! We showed in this tutorial how you can run the React sample application in Docker Compose.