From 76c6826920faa6a70e42d8cf3ee009d007b91e61 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 29 Jul 2026 15:47:40 -0400 Subject: [PATCH] feat(deploy): split compose into prod base + dev override Restructure the container setup to support registry-based deployment: the default docker-compose.yml now pulls a prebuilt app image from the Gitea container registry instead of building locally, while a new docker-compose.dev.yml override preserves the local build + integration test workflow for development. Why: - Production and self-hosting should consume a published image, not rebuild from source on the host. The default `docker compose up` now pulls the app image (git.linuxhg.com/bookhoard/bookhoard) alongside the public postgres image, with no build step required. - Development still needs to build from source and run integration tests, so those concerns move to an override file the Makefile applies. Shared config (env, volumes, ports, healthchecks) lives in one place to avoid drift between environments. Changes: - docker-compose.yml (prod base): the app service now references `image: git.linuxhg.com/bookhoard/bookhoard:${IMAGE_TAG:-latest}` instead of a build context. The tests service is removed (moved to the override). IMAGE_TAG lets deployers pin or roll back a specific version. - docker-compose.dev.yml (new override): adds the local `build:` context for the app and defines the integration `tests` service (profile-gated). Everything else is inherited from the base file via compose merging. - Makefile: introduce a COMPOSE variable that merges the base and override (`-f docker-compose.yml -f docker-compose.dev.yml`); all dev targets now use it. Plain `docker compose` against the base file only remains the production path. - README: quickstart updated to pull and start prebuilt images; clone URL points at the Gitea instance. The development workflow (`make rebuild-app`, `make test-integration`, etc.) is functionally unchanged. --- Makefile | 44 +++++++++++++++++--------------- README.md | 8 +++--- docker-compose.dev.yml | 57 ++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 49 +++--------------------------------- 4 files changed, 89 insertions(+), 69 deletions(-) create mode 100644 docker-compose.dev.yml diff --git a/Makefile b/Makefile index 54e6a0b..cea32de 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,10 @@ endif # Override with: CONTAINER_RUNTIME=podman make rebuild-app CONTAINER_RUNTIME ?= $(shell command -v docker 2>/dev/null || command -v podman 2>/dev/null) +# Dev compose stack: base prod file merged with the dev override (local build + tests). +# Prod deploy does NOT use this — it runs plain `docker compose` against the base file only. +COMPOSE := $(CONTAINER_RUNTIME) compose -f docker-compose.yml -f docker-compose.dev.yml + # Default target help: @echo "Available targets:" @@ -48,9 +52,9 @@ test: # Run integration tests in containers (matches production environment) test-integration: @echo "Building test containers..." - $(CONTAINER_RUNTIME) compose --profile tests build + $(COMPOSE) --profile tests build @echo "Starting application containers..." - $(CONTAINER_RUNTIME) compose up -d db app + $(COMPOSE) up -d db app @echo "Waiting for services to be healthy..." @until $(CONTAINER_RUNTIME) exec bookhoard_db pg_isready -U postgres > /dev/null 2>&1; do \ echo " Database not ready yet..."; \ @@ -64,7 +68,7 @@ test-integration: echo " ✓ Application is ready" @echo "" @echo "Running integration tests in container..." - $(CONTAINER_RUNTIME) compose --profile tests run --rm tests + $(COMPOSE) --profile tests run --rm tests @echo "" @echo "✅ Integration tests completed!" @echo "📝 Containers are still running. Use 'make logs' to view logs or 'make clean' to stop." @@ -75,67 +79,67 @@ test-all: test test-integration # Rebuild app container only (preserve DB, with cache) rebuild-app: @echo "Rebuilding app container (database stays running)..." - $(CONTAINER_RUNTIME) compose up --build --force-recreate -d app + $(COMPOSE) up --build --force-recreate -d app @echo "✓ App container rebuilt and restarted" # Rebuild app container only (preserve DB, no cache) rebuild-app-force: @echo "Force rebuilding app container (database stays running, no cache)..." - $(CONTAINER_RUNTIME) compose build --no-cache app - $(CONTAINER_RUNTIME) compose up --force-recreate -d app + $(COMPOSE) build --no-cache app + $(COMPOSE) up --force-recreate -d app @echo "✓ App container rebuilt and restarted" # Rebuild all containers (preserve DB, with cache) rebuild: @echo "Rebuilding all containers (database preserved)..." - $(CONTAINER_RUNTIME) compose up --build --force-recreate -d + $(COMPOSE) up --build --force-recreate -d @echo "✓ All containers rebuilt and restarted" # Rebuild all containers (preserve DB, no cache) rebuild-force: @echo "Force rebuilding all containers (database preserved, no cache)..." - $(CONTAINER_RUNTIME) compose build --no-cache - $(CONTAINER_RUNTIME) compose up --force-recreate -d + $(COMPOSE) build --no-cache + $(COMPOSE) up --force-recreate -d @echo "✓ All containers rebuilt and restarted" # Rebuild all containers (remove DB, no cache) rebuild-force-db: @echo "Force rebuilding all containers (database will be DELETED, no cache)..." - $(CONTAINER_RUNTIME) compose down -v - $(CONTAINER_RUNTIME) compose build --no-cache - $(CONTAINER_RUNTIME) compose up --force-recreate -d + $(COMPOSE) down -v + $(COMPOSE) build --no-cache + $(COMPOSE) up --force-recreate -d @echo "✓ All containers rebuilt and restarted" # Stop and remove containers clean: - $(CONTAINER_RUNTIME) compose down -v + $(COMPOSE) down -v # Quick start (if already built) up: - $(CONTAINER_RUNTIME) compose up -d + $(COMPOSE) up -d # Stop all containers (alias for clean) down: - $(CONTAINER_RUNTIME) compose down + $(COMPOSE) down # Restart app container (preserves database) restart: @echo "Restarting app container (database stays running)..." - $(CONTAINER_RUNTIME) compose restart app + $(COMPOSE) restart app @echo "✓ App container restarted" # Show container status ps: - $(CONTAINER_RUNTIME) compose ps + $(COMPOSE) ps # Show container logs logs: - $(CONTAINER_RUNTIME) compose logs -f + $(COMPOSE) logs -f # Start containers with test mode enabled for manual testing test-env-up: @echo "Starting containers with test mode enabled..." - TEST_MODE=true RATE_LIMIT_ENABLED=false REQUESTS_PER_MINUTE=1000 $(CONTAINER_RUNTIME) compose up --build --force-recreate -d + TEST_MODE=true RATE_LIMIT_ENABLED=false REQUESTS_PER_MINUTE=1000 $(COMPOSE) up --build --force-recreate -d @echo "Waiting for services to be ready..." @until $(CONTAINER_RUNTIME) exec bookhoard_db pg_isready -U postgres > /dev/null 2>&1; do sleep 1; done @until $(CONTAINER_RUNTIME) exec bookhoard curl -sf http://localhost:8765/health > /dev/null 2>&1; do sleep 1; done @@ -144,7 +148,7 @@ test-env-up: # Stop test environment test-env-down: - $(CONTAINER_RUNTIME) compose down -v + $(COMPOSE) down -v # Verify project guidelines compliance verify-guidelines: diff --git a/README.md b/README.md index 0cf835f..08caec9 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ A modern self-hosted media library system built with Go, PostgreSQL, HTMX, and T ```bash # 1. Clone the repository -git clone https://github.com/yourusername/bookhoard.git +git clone https://git.linuxhg.com/Bookhoard/bookhoard.git cd bookhoard # 2. Set up environment @@ -35,8 +35,10 @@ cp .env.example .env # DBPASS: openssl rand -hex 16 # Edit .env with your generated values -# 3. Start the server -podman-compose up --build -d # or: docker-compose up --build -d +# 3. Pull images and start the server +docker compose pull +docker compose up -d +# Optionally pin a specific version: set IMAGE_TAG in .env (defaults to "latest") # 4. Open your browser open http://localhost:8765 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..8f43223 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,57 @@ +# Development override — merged on top of docker-compose.yml (the base/prod file). +# Activated by all `make` targets via: +# COMPOSE = compose -f docker-compose.yml -f docker-compose.dev.yml +# +# What this adds over prod: +# - Local image BUILDING (prod pulls a prebuilt image from the registry) +# - The integration-tests service (dev only, gated behind the "tests" profile) +# Everything else (env vars, volumes, ports, healthchecks) is inherited from the base file. +services: + # Build the app image locally instead of pulling from the registry + app: + build: + context: . + dockerfile: ./Dockerfile + + # Integration Tests - runs against containerized app and db (dev only) + tests: + build: + context: . + dockerfile: ./Dockerfile + target: test-runner + container_name: bookhoard_tests + environment: + # Database Configuration + DATABASE_HOST: db + DATABASE_PORT: "5432" + DATABASE_USER: postgres + DATABASE_PASSWORD: ${DBPASS} + DATABASE_NAME: bookhoard + COOKIE_SECURE: false + + # Application Configuration + JWT_SECRET: ${JWT_SECRET} + SERVER_PORT: "8765" + + # Test Configuration + TEST_MODE: "true" + RATE_LIMIT_ENABLED: "false" + REQUESTS_PER_MINUTE: 1000 + + # Conversion Service Configuration + BOOKHOARD_CONVERSION_CACHE_DIR: /app/cache/kepub + BOOKHOARD_CONVERSION_TOOL: /usr/bin/kepubify + BOOKHOARD_CONVERSION_CACHE_TTL: 24h + + # Test upload path (inside container) + TEST_UPLOAD_PATH: /app/uploads + depends_on: + db: + condition: service_healthy + app: + condition: service_healthy + volumes: + - ./uploads:/app/uploads + - bookhoard_conversion_cache:/app/cache/kepub + profiles: + - tests diff --git a/docker-compose.yml b/docker-compose.yml index c652ef1..c43d3e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,10 +27,10 @@ services: - .env # Bookhoard Application + # In production this image is pulled from the Gitea container registry. + # Override IMAGE_TAG in .env to pin or rollback a specific version (defaults to "latest"). app: - build: - context: . - dockerfile: ./Dockerfile + image: git.linuxhg.com/bookhoard/bookhoard:${IMAGE_TAG:-latest} container_name: bookhoard environment: # Database Configuration @@ -76,49 +76,6 @@ services: retries: 3 start_period: 10s - # Integration Tests - runs against containerized app and db - tests: - build: - context: . - dockerfile: ./Dockerfile - target: test-runner - container_name: bookhoard_tests - environment: - # Database Configuration - DATABASE_HOST: db - DATABASE_PORT: "5432" - DATABASE_USER: postgres - DATABASE_PASSWORD: ${DBPASS} - DATABASE_NAME: bookhoard - COOKIE_SECURE: false - - # Application Configuration - JWT_SECRET: ${JWT_SECRET} - SERVER_PORT: "8765" - - # Test Configuration - TEST_MODE: "true" - RATE_LIMIT_ENABLED: "false" - REQUESTS_PER_MINUTE: 1000 - - # Conversion Service Configuration - BOOKHOARD_CONVERSION_CACHE_DIR: /app/cache/kepub - BOOKHOARD_CONVERSION_TOOL: /usr/bin/kepubify - BOOKHOARD_CONVERSION_CACHE_TTL: 24h - - # Test upload path (inside container) - TEST_UPLOAD_PATH: /app/uploads - depends_on: - db: - condition: service_healthy - app: - condition: service_healthy - volumes: - - ./uploads:/app/uploads - - bookhoard_conversion_cache:/app/cache/kepub - profiles: - - tests - # Named Volumes volumes: postgres_data: