Создание миграции при запуске приложения в Docker

Как создать создавать миграции, и схему бд при запуске контейнера в Docker ?

Написал такой файл docker-compose, все запускается хорошо, но вот при попытке обращения к базе, и поиска там сущности, таблиц там нет. Как их создать при запуске контейнера (Если их нет) ?

Не бейте камнями, только первый день докер осваиваю

version: '3.4'

networks:
  webapi:
    driver: bridge

services:
  webapi:
    container_name: webapi-dev
    image: ${DOCKER_REGISTRY-}webapi
    build:
      context: .
      dockerfile: webapi/Dockerfile
    ports:
      - "8080:8080"
    networks:
      - "webapi"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    depends_on:
      - postgres_db
      - redis
      - clamav

  postgres_db:
    container_name: postgres
    image: postgres:latest
    ports:
      - "5432:5432"
    networks:
      - "webapi"
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: 123
      POSTGRES_DB: filecryptweb
    volumes:
      - postgres-data:/var/lib/postgresql/data

  redis:
    container_name: redis
    image: redis:latest
    ports:
      - "6379:6379"
    networks:
      - "webapi"

  clamav:
    container_name: clamav
    image: mkodockx/docker-clamav:alpine
    environment:
      CLAMD_CONF_MaxFileSize: 250M
      CLAMD_CONF_MaxScanSize: 250M
      CLAMD_CONF_StreamMaxLength: 250M
    restart: always
    ports:
      - "3310:3310"
    networks:
      - "webapi"

volumes:
  postgres-data:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["webapi/webapi.csproj", "webapi/"]
RUN dotnet restore "./webapi/webapi.csproj"
COPY . .
WORKDIR "/src/webapi"
RUN dotnet build "./webapi.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./webapi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "webapi.dll"]

2024-03-16 04:10:54 webapi-dev  | [21:10:54 INF] { username = , id = , role =  } { path = /api/auth/login, method = POST }
2024-03-16 04:10:54 webapi-dev  | [21:10:54 INF] Executing endpoint 'webapi.Controllers.Account.AuthSessionController.Login (webapi)'
2024-03-16 04:10:54 webapi-dev  | [21:10:54 INF] Route matched with {action = "Login", controller = "AuthSession"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.IActionResult] Login(webapi.DTO.AuthDTO) on controller webapi.Controllers.Account.AuthSessionController (webapi).
2024-03-16 04:10:55 postgres    | 2024-03-15 21:10:55.845 UTC [33] ERROR:  relation "users" does not exist at character 91
2024-03-16 04:10:55 postgres    | 2024-03-15 21:10:55.845 UTC [33] STATEMENT:  SELECT u.id, u.email, u.is_2fa_enabled, u.is_blocked, u.password, u.role, u.username
2024-03-16 04:10:55 postgres    |       FROM users AS u
2024-03-16 04:10:55 postgres    |       WHERE u.email = $1
2024-03-16 04:10:55 postgres    |       LIMIT 1
2024-03-16 04:10:55 webapi-dev  | [21:10:55 ERR] Failed executing DbCommand (99ms) [Parameters=[@__ToLowerInvariant_0='?'], CommandType='Text', CommandTimeout='30']
2024-03-16 04:10:55 webapi-dev  | SELECT u.id, u.email, u.is_2fa_enabled, u.is_blocked, u.password, u.role, u.username
2024-03-16 04:10:55 webapi-dev  | FROM users AS u
2024-03-16 04:10:55 webapi-dev  | WHERE u.email = @__ToLowerInvariant_0
2024-03-16 04:10:55 webapi-dev  | LIMIT 1
2024-03-16 04:10:55 webapi-dev  | [21:10:55 ERR] An exception occurred while iterating over the results of a query for context type 'webapi.DB.FileCryptDbContext'.
2024-03-16 04:10:55 webapi-dev  | Npgsql.PostgresException (0x80004005): 42P01: relation "users" does not exist
2024-03-16 04:10:55 webapi-dev  | 
2024-03-16 04:10:55 webapi-dev  | POSITION: 91
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
2024-03-16 04:10:55 webapi-dev  |    at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
2024-03-16 04:10:55 webapi-dev  |   Exception data:
2024-03-16 04:10:55 webapi-dev  |     Severity: ERROR
2024-03-16 04:10:55 webapi-dev  |     SqlState: 42P01
2024-03-16 04:10:55 webapi-dev  |     MessageText: relation "users" does not exist
2024-03-16 04:10:55 webapi-dev  |     Position: 91
2024-03-16 04:10:55 webapi-dev  |     File: parse_relation.c
2024-03-16 04:10:55 webapi-dev  |     Line: 1449
2024-03-16 04:10:55 webapi-dev  |     Routine: parserOpenTable
2024-03-16 04:10:55 webapi-dev  | Npgsql.PostgresException (0x80004005): 42P01: relation "users" does not exist
2024-03-16 04:10:55 webapi-dev  | 
2024-03-16 04:10:55 webapi-dev  | POSITION: 91
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
2024-03-16 04:10:55 webapi-dev  |    at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
2024-03-16 04:10:55 webapi-dev  |   Exception data:
2024-03-16 04:10:55 webapi-dev  |     Severity: ERROR
2024-03-16 04:10:55 webapi-dev  |     SqlState: 42P01
2024-03-16 04:10:55 webapi-dev  |     MessageText: relation "users" does not exist
2024-03-16 04:10:55 webapi-dev  |     Position: 91
2024-03-16 04:10:55 webapi-dev  |     File: parse_relation.c
2024-03-16 04:10:55 webapi-dev  |     Line: 1449
2024-03-16 04:10:55 webapi-dev  |     Routine: parserOpenTable
2024-03-16 04:10:55 webapi-dev  | [21:10:55 FTL] Npgsql.PostgresException (0x80004005): 42P01: relation "users" does not exist
2024-03-16 04:10:55 webapi-dev  | 
2024-03-16 04:10:55 webapi-dev  | POSITION: 91
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
2024-03-16 04:10:55 webapi-dev  |    at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
2024-03-16 04:10:55 webapi-dev  |    at webapi.DB.Repository`1.GetByFilter(Func`2 queryModifier, CancellationToken cancellationToken) in C:\Users\Stewi\Source\Repos\air2921\FileCryptWeb\webapi\DB\Repository.cs:line 77
2024-03-16 04:10:55 webapi-dev  |   Exception data:
2024-03-16 04:10:55 webapi-dev  |     Severity: ERROR
2024-03-16 04:10:55 webapi-dev  |     SqlState: 42P01
2024-03-16 04:10:55 webapi-dev  |     MessageText: relation "users" does not exist
2024-03-16 04:10:55 webapi-dev  |     Position: 91
2024-03-16 04:10:55 webapi-dev  |     File: parse_relation.c
2024-03-16 04:10:55 webapi-dev  |     Line: 1449
2024-03-16 04:10:55 webapi-dev  |     Routine: parserOpenTable
2024-03-16 04:10:56 webapi-dev  | [21:10:56 INF] Executing ObjectResult, writing value of type '<>f__AnonymousType0`1[[System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'.
2024-03-16 04:10:56 webapi-dev  | [21:10:56 INF] Executed action webapi.Controllers.Account.AuthSessionController.Login (webapi) in 1115.6806ms
2024-03-16 04:10:56 webapi-dev  | [21:10:56 INF] Executed endpoint 'webapi.Controllers.Account.AuthSessionController.Login (webapi)'
2024-03-16 04:10:56 webapi-dev  | [21:10:56 INF] Status Code: 500
2024-03-16 04:10:56 webapi-dev  | [21:10:56 INF] Request finished HTTP/2 POST https://localhost:8081/api/auth/login - 500 null application/json; charset=utf-8 1280.3549ms

Ответы (2 шт):

Автор решения: zb'

Дело в том, что те volumes которые вы указываете в docker-compose не видны в момент сборки (Dockerfile) чтобы запустить миграцию, я думаю имеет смысл делать это в рантайме используя скрипт запущенный из параметра command: docker-compose

т.е. пишем такой примерно start.sh

#!/bin/sh
# если я правильно понимаю - это скрипт миграции
BUILD_CONFIGURATION=Release dotnet publish "./webapi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
dotnet webapi.dll

удаляем соответвующие строки в Dockerfile

в docker-compose примерно так:

version: '3.4'

networks:
  webapi:
    driver: bridge

services:
  webapi:
    container_name: webapi-dev
    image: ${DOCKER_REGISTRY-}webapi
    build:
      context: .
      dockerfile: webapi/Dockerfile
    ports:
      - "8080:8080"
    networks:
      - "webapi"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    depends_on:
      - postgres_db
      - redis
      - clamav
  command: "/bin/sh ./start.sh"

  postgres_db:
    container_name: postgres
    image: postgres:latest
    ports:
      - "5432:5432"
    networks:
      - "webapi"
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: 123
      POSTGRES_DB: filecryptweb
    volumes:
      - postgres-data:/var/lib/postgresql/data

  redis:
    container_name: redis
    image: redis:latest
    ports:
      - "6379:6379"
    networks:
      - "webapi"

  clamav:
    container_name: clamav
    image: mkodockx/docker-clamav:alpine
    environment:
      CLAMD_CONF_MaxFileSize: 250M
      CLAMD_CONF_MaxScanSize: 250M
      CLAMD_CONF_StreamMaxLength: 250M
    restart: always
    ports:
      - "3310:3310"
    networks:
      - "webapi"

volumes:
  postgres-data:

идея делать миграцию в билде не осмысленна, т.к. база то у вас в volume, а это уже часть рантайма, а не билда, поэтому

ещё раз повторю - Docker file это для сборки "железных образов" которые вы собираетесь в идее положить в репозиторий docker и одинаково раскатывать на все инстансы, а docker-compose - конфигурация оркестратора runtime этих контейнеров, "личинка kubrnetics" еcли можно так выразиться, т.е. то что в идее крутится на серваке.

так же вам может понадобиться запускать миграцию только один раз, в этом случае просто выберите как вы узнаете о том что миграцию надо запустить (например версия поменялась) положите этот признак при сборке в контейнер (например в виде файла db_version), проверьте в скрипте запуска что версия совпадает (см чуть позже с чем) и если нет, запустите миграцию и положите в volume (добавьте в compose) содержимое файла версии (вот с этим сравнивать)

→ Ссылка
Автор решения: air2921

Все решилось одной строкой кода.

В моем случае, у меня тело конструктора моего класса DbContext было пустым:

public FileCryptDbContext(DbContextOptions<FileCryptDbContext> options) : base(options)
{
}

Но после добавления проверки на существование базы данных для моего констекста в теле конструктора, таблицы создались:

public FileCryptDbContext(DbContextOptions<FileCryptDbContext> options) : base(options)
{
    Database.EnsureCreated();
}
→ Ссылка