Skip to main content
Bookify is designed to run in Docker containers with all dependencies orchestrated via Docker Compose. This guide covers the complete deployment setup, configuration, and production considerations.

Overview

The Docker setup includes:
  • bookify.api: ASP.NET Core API application
  • bookify-db: PostgreSQL database
  • bookify-redis: Redis cache
  • bookify-idp: Keycloak identity provider
  • bookify-seq: Seq logging server (optional)

Docker Compose Configuration

Base Configuration

The docker-compose.yml defines the core services:
services:
  bookify.api:
    image: ${DOCKER_REGISTRY-}bookifyapi
    build:
      context: .
      dockerfile: src/Bookify.Api/Dockerfile

  bookify-db:
    image: postgres:latest

volumes:
  postgres_bookifydb:

Override Configuration

The docker-compose.override.yml adds development-specific settings:
services:
  bookify.api:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=https://+:443;http://+:80
      - ConnectionStrings__Database=Server=bookify-db;Port=5432;Database=BookifyDb;User Id=postgres;Password=postgres;Include Error Detail=true
    ports:
      - "80"
      - "5001:443"
    depends_on:
      - bookify-db

  bookify-db:
    container_name: Bookify.Db
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=BookifyDb
    restart: always
    ports:
      - "4001:5432"
    volumes:
      - postgres_bookifydb:/var/lib/postgresql/data/

  bookify-idp:
    image: quay.io/keycloak/keycloak:latest
    container_name: Bookify.Identity
    command: start-dev --import-realm
    environment:
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_ADMIN_PASSWORD=admin
    volumes:
      - ./.containers/identity:/opt/keycloak/data
      - ./.files/bookify-realm-export.json:/opt/keycloak/data/import/realm.json
    ports:
      - 18080:8080

  bookify-seq:
    image: datalust/seq:latest
    container_name: Bookify.Seq
    environment:
      - ACCEPT_EULA=Y
    ports:
      - 5341:5341
      - 8081:80

  bookify-redis:
    image: redis:latest
    container_name: Bookify.Redis
    restart: always
    ports:
      - 6379:6379

Dockerfile

The multi-stage Dockerfile (src/Bookify.Api/Dockerfile:1) optimizes the build process:
# Base runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

# Build stage
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["src/Bookify.Api/Bookify.Api.csproj", "src/Bookify.Api/"]
COPY ["src/Bookify.Application/Bookify.Application.csproj", "src/Bookify.Application/"]
COPY ["src/Bookify.Domain/Bookify.Domain.csproj", "src/Bookify.Domain/"]
COPY ["src/Bookify.Infrastructure/Bookify.Infrastructure.csproj", "src/Bookify.Infrastructure/"]
RUN dotnet restore "./src/Bookify.Api/Bookify.Api.csproj"
COPY . .
WORKDIR "/src/src/Bookify.Api"
RUN dotnet build "./Bookify.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build

# Publish stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Bookify.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

# Final stage
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Bookify.Api.dll"]
Build stages:
  1. base: Runtime environment with .NET 9.0 ASP.NET
  2. build: Restore and build the application
  3. publish: Create production-ready binaries
  4. final: Minimal runtime image with published output

Deployment Steps

1
Clone the repository
2
git clone https://github.com/yourusername/bookify.git
cd bookify
3
Build and start all services
4
docker-compose up -d --build
5
This command:
6
  • Builds the bookify.api image from the Dockerfile
  • Pulls required images (PostgreSQL, Redis, Keycloak, Seq)
  • Creates and starts all containers
  • Runs containers in detached mode (-d)
  • 7
    Verify services are running
    8
    docker-compose ps
    
    9
    Expected output:
    10
    NAME                IMAGE                                   STATUS              PORTS
    Bookify.Db          postgres:latest                         Up 30 seconds       0.0.0.0:4001->5432/tcp
    Bookify.Identity    quay.io/keycloak/keycloak:latest       Up 30 seconds       0.0.0.0:18080->8080/tcp
    Bookify.Redis       redis:latest                            Up 30 seconds       0.0.0.0:6379->6379/tcp
    Bookify.Seq         datalust/seq:latest                     Up 30 seconds       0.0.0.0:5341->5341/tcp, 0.0.0.0:8081->80/tcp
    bookify-api-1       bookifyapi                              Up 30 seconds       0.0.0.0:5001->443/tcp, 80/tcp
    
    11
    Check application health
    12
    curl http://localhost:5001/health
    
    13
    Should return:
    14
    {
      "status": "Healthy",
      "entries": {
        "npgsql": { "status": "Healthy" },
        "redis": { "status": "Healthy" },
        "keycloak": { "status": "Healthy" }
      }
    }
    
    15
    Access the API
    16
    The API is now available at:
    17
  • HTTP: http://localhost:80
  • HTTPS: https://localhost:5001
  • Swagger: https://localhost:5001/swagger (development only)
  • Environment Variables

    API Configuration

    VariableDescriptionDefault
    ASPNETCORE_ENVIRONMENTRuntime environmentDevelopment
    ASPNETCORE_URLSListening URLshttps://+:443;http://+:80
    ConnectionStrings__DatabasePostgreSQL connectionSee compose file
    ConnectionStrings__CacheRedis connectionbookify-redis:6379

    Database Configuration

    VariableDescriptionDefault
    POSTGRES_USERDatabase userpostgres
    POSTGRES_PASSWORDDatabase passwordpostgres
    POSTGRES_DBDatabase nameBookifyDb

    Keycloak Configuration

    VariableDescriptionDefault
    KEYCLOAK_ADMINAdmin usernameadmin
    KEYCLOAK_ADMIN_PASSWORDAdmin passwordadmin
    Change default passwords before deploying to production!

    Volume Mounts

    Database Persistence

    volumes:
      - postgres_bookifydb:/var/lib/postgresql/data/
    
    Database data is persisted in a named Docker volume, surviving container restarts and removals.

    Keycloak Data

    volumes:
      - ./.containers/identity:/opt/keycloak/data
      - ./.files/bookify-realm-export.json:/opt/keycloak/data/import/realm.json
    
    • Identity data: Persisted in .containers/identity/
    • Realm configuration: Imported from .files/bookify-realm-export.json

    Service Ports

    ServiceContainer PortHost PortURL
    API (HTTP)8080http://localhost
    API (HTTPS)4435001https://localhost:5001
    PostgreSQL54324001localhost:4001
    Redis63796379localhost:6379
    Keycloak808018080http://localhost:18080
    Seq (Ingestion)53415341http://localhost:5341
    Seq (UI)808081http://localhost:8081

    Common Commands

    Start services

    docker-compose up -d
    

    Stop services

    docker-compose down
    

    Stop and remove volumes

    docker-compose down -v
    
    This deletes all database data and Keycloak configuration!

    View logs

    # All services
    docker-compose logs -f
    
    # Specific service
    docker-compose logs -f bookify.api
    
    # Last 100 lines
    docker-compose logs --tail=100 bookify.api
    

    Rebuild a service

    docker-compose up -d --build bookify.api
    

    Execute commands in containers

    # Access PostgreSQL
    docker exec -it Bookify.Db psql -U postgres -d BookifyDb
    
    # Access Redis CLI
    docker exec -it Bookify.Redis redis-cli
    
    # Shell access to API container
    docker exec -it bookify-api-1 /bin/bash
    

    Scale services

    docker-compose up -d --scale bookify.api=3
    

    Production Considerations

    Security

    Don’t hardcode secrets in docker-compose files:
    services:
      bookify.api:
        environment:
          - ConnectionStrings__Database=${DATABASE_CONNECTION}
          - Keycloak__AdminClientSecret=${KEYCLOAK_ADMIN_SECRET}
    
    Use environment files:
    docker-compose --env-file .env.production up -d
    
    Or Docker secrets:
    services:
      bookify.api:
        secrets:
          - db_password
    secrets:
      db_password:
        external: true
    
    Create separate networks for service layers:
    networks:
      frontend:
      backend:
    
    services:
      bookify.api:
        networks:
          - frontend
          - backend
      
      bookify-db:
        networks:
          - backend  # Not exposed to frontend
    
    Use proper SSL certificates in production:
    services:
      bookify.api:
        environment:
          - ASPNETCORE_URLS=https://+:443
          - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/cert.pfx
          - ASPNETCORE_Kestrel__Certificates__Default__Password=${CERT_PASSWORD}
        volumes:
          - ./certs:/https:ro
    

    Performance

    Resource Limits

    Set CPU and memory limits:
    services:
      bookify.api:
        deploy:
          resources:
            limits:
              cpus: '2'
              memory: 2G
            reservations:
              cpus: '1'
              memory: 1G
    

    Health Checks

    Configure Docker health checks:
    services:
      bookify.api:
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost/health"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 40s
    

    Restart Policy

    Use appropriate restart policies:
    services:
      bookify.api:
        restart: unless-stopped
      bookify-db:
        restart: always
    

    Logging Configuration

    Configure log drivers:
    services:
      bookify.api:
        logging:
          driver: "json-file"
          options:
            max-size: "10m"
            max-file: "3"
    

    Database Management

    Backup strategy:
    # Backup database
    docker exec Bookify.Db pg_dump -U postgres BookifyDb > backup.sql
    
    # Restore database
    docker exec -i Bookify.Db psql -U postgres BookifyDb < backup.sql
    
    Migration strategy:
    • Don’t run migrations automatically in production
    • Use a separate migration job or manual process
    • Test migrations in staging first

    Monitoring

    Add monitoring services:
    services:
      prometheus:
        image: prom/prometheus:latest
        volumes:
          - ./prometheus.yml:/etc/prometheus/prometheus.yml
        ports:
          - 9090:9090
      
      grafana:
        image: grafana/grafana:latest
        ports:
          - 3000:3000
        depends_on:
          - prometheus
    

    Troubleshooting

    Container Won’t Start

    # Check container logs
    docker-compose logs bookify.api
    
    # Inspect container
    docker inspect bookify-api-1
    
    # Check resource usage
    docker stats
    

    Port Already in Use

    ERROR: for bookify.api  Cannot start service: Ports are not available
    
    Solution: Change port mapping in docker-compose.override.yml:
    ports:
      - "5002:443"  # Use different host port
    

    Database Connection Failed

    Npgsql.NpgsqlException: Failed to connect
    
    Solutions:
    1. Ensure database container is running: docker ps | grep bookify-db
    2. Check service dependencies are correct
    3. Use service name (not localhost) in connection string
    4. Verify network connectivity: docker-compose exec bookify.api ping bookify-db

    Image Build Failed

    # Clear build cache
    docker-compose build --no-cache bookify.api
    
    # Remove old images
    docker image prune -a
    

    Next Steps

    Health Checks

    Monitor container health

    Architecture

    Understand the application structure

    Build docs developers (and LLMs) love