Continuing the containerization
Last year I wrote about how to set up a local reverse proxy with nginx and mkcert via Docker-Compose.
Being able to spin up a local, production-like reverse proxy to use while developing is great, but why stop there?
Sooner or later the need for a database to store the applications data will emerge, and with any data structure, the need for change - adding, updating or deleting elements of the structure is needed. In other words, the need for migrations.
Back in the day, a usual practice for a team depending on a database (depending on the complexity of the application and maturity of the team), was to share a database instance for development. Setting up a local database can be tricky, and up until 2017 Microsoft's SQL Server was only available on the Windows platform, requiring a VM for local development for *nix users. The single, shared instance strategy is also quite limiting for teams working in parallel on tasks requiring database and / or application code changes. A developer testing a database change on a single branch can easily break the main branch when a single instance is used.
In 2017 Microsoft release SQL Server 2017 (and now 2019) with Linux support, and with it, thankfully, Docker support.
A clean instance of MS SQL 2019 can be added to a
docker-compose setup as easily as this:
docker-compose, every developer on a team can have their own version of the database.
Running migrations with FluentMigrator
A common pattern for handling migrations is creating a dedicated .NET
csproj file where the migrations live. Let's call it
After writing some migrations, the easiest way of running them is via the FluentMigrator dotnet tool dotnet-fm. With
dotnet-fm, running migrations is as easy as
dotnet-fm migrate --processor SqlServer2016 --assembly MyApp.Migrations.dll --connection "Data Source=myConnectionString"`
Bootstrapping the database with Docker-Compose
Now we have a
docker-compose containing an MS SQL instance, and we have a
csproj file containing some database migrations. To save us from having to run the migrations manually, the process of bootstrapping the development environment can be automated by containerizing the process running the migrations. For this, we create a
Dockerfile that compiles the migration project, grabs the
dotnet-fm tool for running FluentMigrator and wraps it up with an
entrypoint for running.
Some things to notice here.
The FluentMigrator library (installed with NuGet) and the
dotnet-fm tool needs to be the same version, so the
sed command on line 7 grabs the version-string from the
csproj file and uses it to install the correct version of
dotnet-fm tool on line 9.
On line 12 a little shell-script called
wait-for is cloned. https://github.com/eficode/wait-for.git is used to wrap the execution of
dotnet-fm and wait for the database the become available. This is a neat trick to handle the timing issues that can occur when running via
docker-compose, where the migrations can be executed before the database is ready.
Dockerfile is multi-stage, so the compiled library,
dotnet-fm binary and
wait-for script is copied to a
dotnet runtime image. The
netcat package installed on line 17 is a runtime dependency for
Dockerfile in place, the final
The whole thing spins up with
docker-compose up. When
db is ready, the migrations will be run and bootstraps the database.