diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd72f90..62fca62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [3.3.9] - 2026-04-26
+
+### Fixed
+
+- **Startup / database:** FluentMigrator (`EnsureCertsEngineMigratedAsync`) now runs in `Program.cs` immediately after `WebApplication.Build()` and before `RunAsync`, so schema (including `app_runtime_leases`) exists before any `IHostedService` starts. `InitializationHostedService` only performs bootstrap lease + identity init.
+
## [3.3.8] - 2026-04-26
### Fixed
diff --git a/src/MaksIT.CertsUI.Engine/Extensions/ApplicationBuilderExtensions.cs b/src/MaksIT.CertsUI.Engine/Extensions/ApplicationBuilderExtensions.cs
index 1aec7af..8ea5ab9 100644
--- a/src/MaksIT.CertsUI.Engine/Extensions/ApplicationBuilderExtensions.cs
+++ b/src/MaksIT.CertsUI.Engine/Extensions/ApplicationBuilderExtensions.cs
@@ -4,11 +4,11 @@ using Microsoft.Extensions.Hosting;
namespace MaksIT.CertsUI.Engine.Extensions;
///
-/// DB migrations are handled by FluentMigrator and optional schema sync from InitializationHostedService.
-/// This method is a no-op for backward compatibility with host startup.
+/// DB migrations run in Program.cs via before RunAsync.
+/// This method is a no-op kept for backward compatibility with older host wiring.
///
public static class ApplicationBuilderExtensions {
public static void AddCertsEngineMigrations(this IHost host) {
- // No-op: migrations and schema sync run from InitializationHostedService via IRunMigrationsService and ISchemaSyncService.
+ // No-op: see Program.cs (migrations) and InitializationHostedService (identity bootstrap under lease).
}
}
diff --git a/src/MaksIT.CertsUI.Engine/Extensions/ServiceCollectionExtensions.cs b/src/MaksIT.CertsUI.Engine/Extensions/ServiceCollectionExtensions.cs
index b2da0fa..9622088 100644
--- a/src/MaksIT.CertsUI.Engine/Extensions/ServiceCollectionExtensions.cs
+++ b/src/MaksIT.CertsUI.Engine/Extensions/ServiceCollectionExtensions.cs
@@ -67,7 +67,7 @@ public static class ServiceCollectionExtensions {
#region Host initialization helpers
///
- /// Runs FluentMigrator then optional add-only schema sync (when is true). Called from host startup (e.g. InitializationHostedService).
+ /// Runs FluentMigrator then optional add-only schema sync (when is true). Called from Program.cs before RunAsync.
///
public static async Task EnsureCertsEngineMigratedAsync(this IServiceProvider serviceProvider) {
await using var scope = serviceProvider.CreateAsyncScope();
diff --git a/src/MaksIT.CertsUI/HostedServices/InitializationHostedService.cs b/src/MaksIT.CertsUI/HostedServices/InitializationHostedService.cs
index fc1e4dc..0516686 100644
--- a/src/MaksIT.CertsUI/HostedServices/InitializationHostedService.cs
+++ b/src/MaksIT.CertsUI/HostedServices/InitializationHostedService.cs
@@ -1,15 +1,14 @@
using Microsoft.Extensions.Options;
using MaksIT.CertsUI.Engine.DomainServices;
-using MaksIT.CertsUI.Engine.Extensions;
using MaksIT.CertsUI.Engine.Infrastructure;
using MaksIT.CertsUI.Engine.RuntimeCoordination;
namespace MaksIT.CertsUI.HostedServices;
///
-/// Runs startup initialization (migrations + identity bootstrap) before the API starts serving requests.
-/// FluentMigrator runs first on every instance (same pattern as Vault); the bootstrap lease then ensures
-/// only one replica performs identity bootstrap against shared .
+/// Runs identity bootstrap before the API starts serving requests. FluentMigrator already ran in Program.cs
+/// before the host starts. The bootstrap lease ensures only one replica writes against shared
+/// .
///
public sealed class InitializationHostedService(
ILogger logger,
@@ -23,18 +22,11 @@ public sealed class InitializationHostedService(
public async Task StartAsync(CancellationToken cancellationToken) {
const int delayMilliseconds = 2000;
- var migrationsApplied = false;
while (!cancellationToken.IsCancellationRequested) {
try {
logger.LogInformation("Running startup initialization...");
- // Migrations must run before lease acquisition: app_runtime_leases is created by FluentMigrator.
- if (!migrationsApplied) {
- await serviceProvider.EnsureCertsEngineMigratedAsync().ConfigureAwait(false);
- migrationsApplied = true;
- }
-
var holder = runtimeInstance.InstanceId;
var acquired = await runtimeLease.TryAcquireAsync(RuntimeLeaseNames.Bootstrap, holder, BootstrapLeaseTtl, cancellationToken).ConfigureAwait(false);
if (!acquired.IsSuccess)
diff --git a/src/MaksIT.CertsUI/MaksIT.CertsUI.csproj b/src/MaksIT.CertsUI/MaksIT.CertsUI.csproj
index b03c9c6..e446068 100644
--- a/src/MaksIT.CertsUI/MaksIT.CertsUI.csproj
+++ b/src/MaksIT.CertsUI/MaksIT.CertsUI.csproj
@@ -1,7 +1,7 @@
- 3.3.8
+ 3.3.9
net10.0
enable
enable
diff --git a/src/MaksIT.CertsUI/Program.cs b/src/MaksIT.CertsUI/Program.cs
index 56bfaa8..2866dcf 100644
--- a/src/MaksIT.CertsUI/Program.cs
+++ b/src/MaksIT.CertsUI/Program.cs
@@ -135,6 +135,9 @@ builder.Services.AddHealthChecks()
var app = builder.Build();
+// FluentMigrator must complete before any IHostedService starts; bootstrap lease uses app_runtime_leases.
+await app.Services.EnsureCertsEngineMigratedAsync();
+
app.UseMiddleware();
app.AddCertsEngineMigrations();
@@ -169,4 +172,4 @@ app.MapGet("/health/ready", async (CancellationToken ct) => {
}
});
-app.Run();
+await app.RunAsync();