A popular Docker article describes a pattern to initialize stateful container data at runtime. The example uses a Shell script to run the initialization and then pass control to the container’s main command. We can use the same pattern to initialize a Docker Windows container.
My example
In keeping with the original example, we’ll initialize data into a database container. This pattern will work for any container though. As we’re using Windows containers, I’ll use the SQL Server (Express) image.
As a side note, it’s not as easy as it should be to initialize a SQL Server container. I’ve followed Tometchy’s excellent Initialize MS SQL in Docker container – create database at startup article. My changes are:
- I’m using Windows containers
- To demonstrate a point, I want to initialize the database and then run the regular start command
How to initialize a Windows container
First, create a Powershell script that runs the initialization code and then passes control back to a long running process:
$cmd = $args ###################### # Start initialization ###################### Write-Host "Starting SQL Server for initialization" start-service MSSQL`$SQLEXPRESS if($sa_password -ne "_") { Write-Verbose "Changing SA login credentials" $sqlcmd = "ALTER LOGIN sa with password='$($env:sa_password)';ALTER LOGIN sa ENABLE;" & sqlcmd -Q $sqlcmd } Write-Host "Running initialization script" sqlcmd -S localhost -U sa -P $env:sa_password -d master -i c:\init.d\create-database.sql Write-Host "SQL Server initialized. Stopping..." stop-service MSSQL`$SQLEXPRESS #################### # End initialization #################### Write-Host "Running command: $($cmd)" Invoke-Expression -Command "$cmd" # or... # cmd.exe /c "$cmd"
What’s happening here is:
- Line 1: Capture arguments to this script’s invocation. We expect this to be the command we want to run after initialization
- Line 3-23: Initialization code. In this case we start SQL Server, set an sa password so we can login to it, run the create-database.sql and then stop SQL Server. You can populate this section with any initialization code you need.
- Line 27: Invoke the script argument as a Powershell command. I’ve shown the equivalent for cmd.exe in line 29.
To execute this code on container startup, simply set the script as the ENTRYPOINT and the original command as the CMD. Here’s what it looks like as Docker Compose:
version: '3' services: sqlserver: image: microsoft/mssql-server-windows-express:latest environment: ACCEPT_EULA: Y sa_password: "C;2nXv#w" ports: - "1433:1433" volumes: - ./init.d:C:/init.d entrypoint: "powershell -File C:\\\\init.d\\\\run-initialization.ps1" command: ".\\\\start -sa_password $$env:sa_password -ACCEPT_EULA $$env:ACCEPT_EULA -attach_dbs \\\"$$env:attach_dbs\\\" -Verbose"
Note that the command here is the default CMD of the microsoft/mssql-server-windows-express image. Just watch the escaping of $ and \ characters here.
When this container starts, its command is the ENTRYPOINT with the CMD as command line arguments to it. That is:
powershell -File C:\init.d\run-initialization.ps1 .\start -sa_password $env:sa_password -ACCEPT_EULA $env:ACCEPT_EULA -attach_dbs \"$env:attach_dbs\" -Verbose
What actually happens is run-initialization.ps1 does the database initialization work and then invokes everything afterwards as a new Powershell command. That is, it starts the database.
Try it!
Pull the example code from GitHub. I ran it on my Windows 10 (1903) laptop. It should also work in Windows Server 2019.
[…] « Initialize a Windows container with an entrypoint script […]