Running EF Core Migration Bundle on Alpine Linux - problems and optimisations

Running EF Core Migration Bundle on Alpine Linux - problems and optimisations

Entity Framework Core Migration Bundles can greatly ease the process of applying database changes during the deployment of your application. If this is the first time you're hearing about them, there's a short introduction in one of my earlier posts.

Recently, I wanted to run the bundle inside a Docker container running an image based on the Alpine Linux distro. Since they are supposed to be self-contained, I did not expect any problems to appear. However, that was not the case.

Issue #1

Error loading shared library ld-linux-x86-64.so.2: No such file or directory (needed by ./tmp/migrations-artifact/efbundle)
Error relocating ./tmp/migrations-artifact/efbundle: __strdup: symbol not found
Error relocating ./tmp/migrations-artifact/efbundle: __isnan: symbol not found
Error relocating ./tmp/migrations-artifact/efbundle: __isnanf: symbol not found

This looks like there are some dependencies missing. Thankfully, Googling this one resulted in finding a simple fix. Running apk add gcompat before executing the bundle solved the problem.

Issue #2

An error occurred while accessing the Microsoft.Extensions.Hosting services. 
Continuing without the application service provider. 
Error: Value cannot be null. (Parameter 'implementationInstance')
Unable to create an object of type 'ApplicationDbContext'. 
For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

After digging into the exception, it turned out that this piece of code was causing it:

public class Startup
{
    private IConfiguration _configuration { get; }

    public Startup(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        var someConfig = _configuration.GetSection("someConfig").Get<SomeConfig>();
        services.AddSingleton(someConfig);
        ...
    }
}

Because appsettings.json was not present, the someConfig variable was null and that was the problem. There were a few possible fixes. First, the code in the Startup class could be changed so that no nulls would be added to the service provider. On the other hand, you might want to prevent application startup if the configuration is missing. Another option would be copying a dummy appsettings.json file alongside the bundle executable. It would most likely solve the problem but seems a bit hacky.

There must be a better way - the bundle shouldn't need to go through all the clutter in Configure and ConfigureServices just to run the migrations. There is a great article on Andrew Lock's blog called Configuring environment specific services in ASP.NET Core - Part 2. In it, there is a section Environment-Specific method conventions which helps accomplish exactly that.

By adding the two following methods to the Startup class, the bundle is able to skip all the startup code it doesn't have any business in. It should only require the DbContext to be configured.

public void ConfigureBuildDeploymentServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;"));
}

public void ConfigureBuildDeployment(IApplicationBuilder app, IWebHostEnvironment env)
{ }

Note the dummy connection string in there - that's on purpose. You can provide the connection string as a parameter to the bundle with the --connection switch. A connection string is necessary because otherwise, the setup will fail, but the dummy string works very well.

Now it's just a matter of setting the ASPNETCORE_ENVIRONMENT variable to BuildDeployment and voilà! The error is gone.

Side note: A similar thing occurs when the bundle is built - the application is started and goes through all the code in the Startup class. The exception isn't thrown there because the configuration is available. By adding these two simple methods and setting the environment accordingly, the build is faster and more resilient as it skips the unnecessary code. Two birds with one stone!

Side note #2: Make sure the AddDbContext call is the same as your regular one (bar the connection string). If it's not, the migrations might fail because of wrong configuration or missing extensions.

Issue #3

The last issue doesn't have a stack trace or an error message. The problem was simple - the bundle was starting the application and then hanging. After adding some logging, it was clear the bundle was starting and then doing nothing.

The migration bundle was built with a linux-x64 runtime ID, which fit the image the Docker container was running on. I tried running the bundle in a container running Ubuntu image, and it worked fine. That meant that something in the Alpine Linux must be causing this. While looking through the available runtime identifiers, you can notice that there are separate Runtime IDs for the small distro.

And guess what - rebuilding the bundle with alpine-x64 as target runtime fixes the problem and migrations were applied.


Overall, the migrations bundles are a great feature of Entity Framework Core. There are still a couple of issues to be ironed out, but thankfully none of them are deal-breakers. At least for my use-cases!

Cover photo by Marco Meyer on Unsplash

Show Comments