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.
This commit is contained in:
@@ -11,6 +11,10 @@ endif
|
|||||||
# Override with: CONTAINER_RUNTIME=podman make rebuild-app
|
# Override with: CONTAINER_RUNTIME=podman make rebuild-app
|
||||||
CONTAINER_RUNTIME ?= $(shell command -v docker 2>/dev/null || command -v podman 2>/dev/null)
|
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
|
# Default target
|
||||||
help:
|
help:
|
||||||
@echo "Available targets:"
|
@echo "Available targets:"
|
||||||
@@ -48,9 +52,9 @@ test:
|
|||||||
# Run integration tests in containers (matches production environment)
|
# Run integration tests in containers (matches production environment)
|
||||||
test-integration:
|
test-integration:
|
||||||
@echo "Building test containers..."
|
@echo "Building test containers..."
|
||||||
$(CONTAINER_RUNTIME) compose --profile tests build
|
$(COMPOSE) --profile tests build
|
||||||
@echo "Starting application containers..."
|
@echo "Starting application containers..."
|
||||||
$(CONTAINER_RUNTIME) compose up -d db app
|
$(COMPOSE) up -d db app
|
||||||
@echo "Waiting for services to be healthy..."
|
@echo "Waiting for services to be healthy..."
|
||||||
@until $(CONTAINER_RUNTIME) exec bookhoard_db pg_isready -U postgres > /dev/null 2>&1; do \
|
@until $(CONTAINER_RUNTIME) exec bookhoard_db pg_isready -U postgres > /dev/null 2>&1; do \
|
||||||
echo " Database not ready yet..."; \
|
echo " Database not ready yet..."; \
|
||||||
@@ -64,7 +68,7 @@ test-integration:
|
|||||||
echo " ✓ Application is ready"
|
echo " ✓ Application is ready"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Running integration tests in container..."
|
@echo "Running integration tests in container..."
|
||||||
$(CONTAINER_RUNTIME) compose --profile tests run --rm tests
|
$(COMPOSE) --profile tests run --rm tests
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "✅ Integration tests completed!"
|
@echo "✅ Integration tests completed!"
|
||||||
@echo "📝 Containers are still running. Use 'make logs' to view logs or 'make clean' to stop."
|
@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 container only (preserve DB, with cache)
|
||||||
rebuild-app:
|
rebuild-app:
|
||||||
@echo "Rebuilding app container (database stays running)..."
|
@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"
|
@echo "✓ App container rebuilt and restarted"
|
||||||
|
|
||||||
# Rebuild app container only (preserve DB, no cache)
|
# Rebuild app container only (preserve DB, no cache)
|
||||||
rebuild-app-force:
|
rebuild-app-force:
|
||||||
@echo "Force rebuilding app container (database stays running, no cache)..."
|
@echo "Force rebuilding app container (database stays running, no cache)..."
|
||||||
$(CONTAINER_RUNTIME) compose build --no-cache app
|
$(COMPOSE) build --no-cache app
|
||||||
$(CONTAINER_RUNTIME) compose up --force-recreate -d app
|
$(COMPOSE) up --force-recreate -d app
|
||||||
@echo "✓ App container rebuilt and restarted"
|
@echo "✓ App container rebuilt and restarted"
|
||||||
|
|
||||||
# Rebuild all containers (preserve DB, with cache)
|
# Rebuild all containers (preserve DB, with cache)
|
||||||
rebuild:
|
rebuild:
|
||||||
@echo "Rebuilding all containers (database preserved)..."
|
@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"
|
@echo "✓ All containers rebuilt and restarted"
|
||||||
|
|
||||||
# Rebuild all containers (preserve DB, no cache)
|
# Rebuild all containers (preserve DB, no cache)
|
||||||
rebuild-force:
|
rebuild-force:
|
||||||
@echo "Force rebuilding all containers (database preserved, no cache)..."
|
@echo "Force rebuilding all containers (database preserved, no cache)..."
|
||||||
$(CONTAINER_RUNTIME) compose build --no-cache
|
$(COMPOSE) build --no-cache
|
||||||
$(CONTAINER_RUNTIME) compose up --force-recreate -d
|
$(COMPOSE) up --force-recreate -d
|
||||||
@echo "✓ All containers rebuilt and restarted"
|
@echo "✓ All containers rebuilt and restarted"
|
||||||
|
|
||||||
# Rebuild all containers (remove DB, no cache)
|
# Rebuild all containers (remove DB, no cache)
|
||||||
rebuild-force-db:
|
rebuild-force-db:
|
||||||
@echo "Force rebuilding all containers (database will be DELETED, no cache)..."
|
@echo "Force rebuilding all containers (database will be DELETED, no cache)..."
|
||||||
$(CONTAINER_RUNTIME) compose down -v
|
$(COMPOSE) down -v
|
||||||
$(CONTAINER_RUNTIME) compose build --no-cache
|
$(COMPOSE) build --no-cache
|
||||||
$(CONTAINER_RUNTIME) compose up --force-recreate -d
|
$(COMPOSE) up --force-recreate -d
|
||||||
@echo "✓ All containers rebuilt and restarted"
|
@echo "✓ All containers rebuilt and restarted"
|
||||||
|
|
||||||
# Stop and remove containers
|
# Stop and remove containers
|
||||||
clean:
|
clean:
|
||||||
$(CONTAINER_RUNTIME) compose down -v
|
$(COMPOSE) down -v
|
||||||
|
|
||||||
# Quick start (if already built)
|
# Quick start (if already built)
|
||||||
up:
|
up:
|
||||||
$(CONTAINER_RUNTIME) compose up -d
|
$(COMPOSE) up -d
|
||||||
|
|
||||||
# Stop all containers (alias for clean)
|
# Stop all containers (alias for clean)
|
||||||
down:
|
down:
|
||||||
$(CONTAINER_RUNTIME) compose down
|
$(COMPOSE) down
|
||||||
|
|
||||||
# Restart app container (preserves database)
|
# Restart app container (preserves database)
|
||||||
restart:
|
restart:
|
||||||
@echo "Restarting app container (database stays running)..."
|
@echo "Restarting app container (database stays running)..."
|
||||||
$(CONTAINER_RUNTIME) compose restart app
|
$(COMPOSE) restart app
|
||||||
@echo "✓ App container restarted"
|
@echo "✓ App container restarted"
|
||||||
|
|
||||||
# Show container status
|
# Show container status
|
||||||
ps:
|
ps:
|
||||||
$(CONTAINER_RUNTIME) compose ps
|
$(COMPOSE) ps
|
||||||
|
|
||||||
# Show container logs
|
# Show container logs
|
||||||
logs:
|
logs:
|
||||||
$(CONTAINER_RUNTIME) compose logs -f
|
$(COMPOSE) logs -f
|
||||||
|
|
||||||
# Start containers with test mode enabled for manual testing
|
# Start containers with test mode enabled for manual testing
|
||||||
test-env-up:
|
test-env-up:
|
||||||
@echo "Starting containers with test mode enabled..."
|
@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..."
|
@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_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
|
@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
|
# Stop test environment
|
||||||
test-env-down:
|
test-env-down:
|
||||||
$(CONTAINER_RUNTIME) compose down -v
|
$(COMPOSE) down -v
|
||||||
|
|
||||||
# Verify project guidelines compliance
|
# Verify project guidelines compliance
|
||||||
verify-guidelines:
|
verify-guidelines:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ A modern self-hosted media library system built with Go, PostgreSQL, HTMX, and T
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Clone the repository
|
# 1. Clone the repository
|
||||||
git clone https://github.com/yourusername/bookhoard.git
|
git clone https://git.linuxhg.com/Bookhoard/bookhoard.git
|
||||||
cd bookhoard
|
cd bookhoard
|
||||||
|
|
||||||
# 2. Set up environment
|
# 2. Set up environment
|
||||||
@@ -35,8 +35,10 @@ cp .env.example .env
|
|||||||
# DBPASS: openssl rand -hex 16
|
# DBPASS: openssl rand -hex 16
|
||||||
# Edit .env with your generated values
|
# Edit .env with your generated values
|
||||||
|
|
||||||
# 3. Start the server
|
# 3. Pull images and start the server
|
||||||
podman-compose up --build -d # or: docker-compose up --build -d
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
# Optionally pin a specific version: set IMAGE_TAG in .env (defaults to "latest")
|
||||||
|
|
||||||
# 4. Open your browser
|
# 4. Open your browser
|
||||||
open http://localhost:8765
|
open http://localhost:8765
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Development override — merged on top of docker-compose.yml (the base/prod file).
|
||||||
|
# Activated by all `make` targets via:
|
||||||
|
# COMPOSE = <runtime> 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
|
||||||
+3
-46
@@ -27,10 +27,10 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
|
|
||||||
# Bookhoard Application
|
# 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:
|
app:
|
||||||
build:
|
image: git.linuxhg.com/bookhoard/bookhoard:${IMAGE_TAG:-latest}
|
||||||
context: .
|
|
||||||
dockerfile: ./Dockerfile
|
|
||||||
container_name: bookhoard
|
container_name: bookhoard
|
||||||
environment:
|
environment:
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
@@ -76,49 +76,6 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
start_period: 10s
|
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
|
# Named Volumes
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|||||||
Reference in New Issue
Block a user