diff --git a/Dockerfile b/Dockerfile index 7b4a3f8..2c48584 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,8 @@ COPY admin/ . RUN npm run build # Go build stage -FROM golang:1.24-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder +ARG TARGETARCH # Install build dependencies RUN apk add --no-cache git ca-certificates tzdata @@ -34,10 +35,10 @@ RUN go mod download COPY . . # Build the API binary -RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o /app/api ./cmd/api +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -ldflags="-w -s" -o /app/api ./cmd/api # Build the worker binary -RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o /app/worker ./cmd/worker +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -ldflags="-w -s" -o /app/worker ./cmd/worker # Base runtime stage for Go services FROM alpine:3.19 AS go-base diff --git a/Makefile b/Makefile index 6acf5e4..f6a571a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,6 @@ # Binary names API_BINARY=casera-api WORKER_BINARY=casera-worker -ADMIN_BINARY=casera-admin # Build flags LDFLAGS=-ldflags "-s -w" @@ -24,12 +23,8 @@ build: build-worker: go build $(LDFLAGS) -o bin/$(WORKER_BINARY) ./cmd/worker -# Build the admin binary -build-admin: - go build $(LDFLAGS) -o bin/$(ADMIN_BINARY) ./cmd/admin - # Build all binaries -build-all: build build-worker build-admin +build-all: build build-worker # Run the API server run: @@ -39,10 +34,6 @@ run: run-worker: go run ./cmd/worker -# Run the admin -run-admin: - go run ./cmd/admin - # Run tests test: go test -v -race -cover ./... @@ -73,24 +64,30 @@ fmt: vet: go vet ./... -# Docker commands +# Docker commands (dev — uses docker-compose.dev.yml) docker-build: - docker-compose build + docker compose -f docker-compose.dev.yml build docker-up: - docker-compose up -d + docker compose -f docker-compose.dev.yml up -d docker-down: - docker-compose down + docker compose -f docker-compose.dev.yml down docker-logs: - docker-compose logs -f + docker compose -f docker-compose.dev.yml logs -f docker-dev: - docker-compose -f docker-compose.yml -f docker-compose.dev.yml up --build + docker compose -f docker-compose.dev.yml up --build docker-restart: - docker-compose down && docker-compose up -d + docker compose -f docker-compose.dev.yml down && docker compose -f docker-compose.dev.yml up -d + +# Docker commands (prod — builds production images) +docker-build-prod: + docker build --target api -t $${REGISTRY:-ghcr.io/treytartt}/casera-api:$${TAG:-latest} . + docker build --target worker -t $${REGISTRY:-ghcr.io/treytartt}/casera-worker:$${TAG:-latest} . + docker build --target admin -t $${REGISTRY:-ghcr.io/treytartt}/casera-admin:$${TAG:-latest} . # Database migrations migrate-up: @@ -116,13 +113,12 @@ help: @echo "Build:" @echo " deps - Install dependencies" @echo " build - Build API binary" - @echo " build-all - Build all binaries (API, Worker, Admin)" + @echo " build-all - Build all binaries (API, Worker)" @echo " clean - Clean build artifacts" @echo "" @echo "Run:" @echo " run - Run API server" @echo " run-worker - Run background worker" - @echo " run-admin - Run admin panel" @echo "" @echo "Test & Lint:" @echo " test - Run tests" @@ -131,13 +127,16 @@ help: @echo " fmt - Format code" @echo " vet - Vet code" @echo "" - @echo "Docker:" - @echo " docker-build - Build Docker images" - @echo " docker-up - Start Docker containers" - @echo " docker-down - Stop Docker containers" - @echo " docker-logs - View Docker logs" - @echo " docker-dev - Start in development mode" - @echo " docker-restart- Restart all containers" + @echo "Docker (dev):" + @echo " docker-build - Build dev Docker images" + @echo " docker-up - Start dev containers (detached)" + @echo " docker-down - Stop dev containers" + @echo " docker-logs - View dev container logs" + @echo " docker-dev - Build and start dev containers (foreground)" + @echo " docker-restart - Restart dev containers" + @echo "" + @echo "Docker (prod):" + @echo " docker-build-prod - Build production images (api, worker, admin)" @echo "" @echo "Database:" @echo " migrate-up - Run database migrations" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 3d31fb8..b834022 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,40 +1,189 @@ -# Development configuration - use with: -# docker-compose -f docker-compose.yml -f docker-compose.dev.yml up +# Local development compose file (self-contained, no base file needed) +# Usage: +# docker compose -f docker-compose.dev.yml up --build services: + # PostgreSQL Database db: + image: postgres:16-alpine + container_name: casera-db + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-casera} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-casera_dev_password} + POSTGRES_DB: ${POSTGRES_DB:-casera} + volumes: + - postgres_data:/var/lib/postgresql/data ports: - - "5433:5432" # Use 5433 to avoid conflicts with other projects + - "${DB_PORT:-5433}:5432" # 5433 externally to avoid conflicts with local postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-casera} -d ${POSTGRES_DB:-casera}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - casera-network + # Redis Cache redis: + image: redis:7-alpine + container_name: casera-redis + restart: unless-stopped + command: redis-server --appendonly yes + volumes: + - redis_data:/data ports: - - "6380:6379" # Use 6380 to avoid conflicts + - "${REDIS_PORT:-6379}:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - casera-network + # Casera API api: build: context: . target: api - environment: - DEBUG: "true" - volumes: - - ./:/app/src:ro # Mount source for debugging + container_name: casera-api + restart: unless-stopped ports: - - "8000:8000" + - "${PORT:-8000}:8000" + environment: + # Server + PORT: "8000" + DEBUG: "true" + ALLOWED_HOSTS: "localhost,127.0.0.1" + TIMEZONE: "${TIMEZONE:-UTC}" + # Database + DB_HOST: db + DB_PORT: "5432" + POSTGRES_USER: ${POSTGRES_USER:-casera} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-casera_dev_password} + POSTGRES_DB: ${POSTGRES_DB:-casera} + DB_SSLMODE: "disable" + + # Redis + REDIS_URL: "redis://redis:6379/0" + + # Security + SECRET_KEY: ${SECRET_KEY:-dev-secret-key-change-in-production-min-32-chars} + + # Email + EMAIL_HOST: ${EMAIL_HOST:-smtp.gmail.com} + EMAIL_PORT: ${EMAIL_PORT:-587} + EMAIL_HOST_USER: ${EMAIL_HOST_USER} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} + DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL:-Casera } + EMAIL_USE_TLS: "true" + + # Push Notifications + APNS_AUTH_KEY_PATH: ${APNS_AUTH_KEY_PATH} + APNS_AUTH_KEY_ID: ${APNS_AUTH_KEY_ID} + APNS_TEAM_ID: ${APNS_TEAM_ID} + APNS_TOPIC: ${APNS_TOPIC:-com.example.casera} + APNS_USE_SANDBOX: "true" + FCM_SERVER_KEY: ${FCM_SERVER_KEY} + volumes: + - ./push_certs:/certs:ro + - ./uploads:/app/uploads + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:8000/api/health/"] + interval: 30s + timeout: 10s + start_period: 10s + retries: 3 + networks: + - casera-network + + # Casera Admin Panel (Next.js) admin: build: context: . target: admin - environment: - NEXT_PUBLIC_API_URL: "http://localhost:8000" + container_name: casera-admin + restart: unless-stopped ports: - - "3000:3000" + - "${ADMIN_PORT:-3000}:3000" + environment: + PORT: "3000" + HOSTNAME: "0.0.0.0" + NEXT_PUBLIC_API_URL: "${NEXT_PUBLIC_API_URL:-http://api:8000}" + depends_on: + api: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/admin/"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - casera-network + # Casera Worker (Background Jobs) worker: build: context: . target: worker + container_name: casera-worker + restart: unless-stopped environment: - DEBUG: "true" + # Database + DB_HOST: db + DB_PORT: "5432" + POSTGRES_USER: ${POSTGRES_USER:-casera} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-casera_dev_password} + POSTGRES_DB: ${POSTGRES_DB:-casera} + DB_SSLMODE: "disable" + + # Redis + REDIS_URL: "redis://redis:6379/0" + + # Security + SECRET_KEY: ${SECRET_KEY:-dev-secret-key-change-in-production-min-32-chars} + + # Push Notifications + APNS_AUTH_KEY_PATH: "/certs/apns_key.p8" + APNS_AUTH_KEY_ID: ${APNS_AUTH_KEY_ID} + APNS_TEAM_ID: ${APNS_TEAM_ID} + APNS_TOPIC: ${APNS_TOPIC:-com.example.casera} + APNS_USE_SANDBOX: "true" + FCM_SERVER_KEY: ${FCM_SERVER_KEY} + + # Email + EMAIL_HOST: ${EMAIL_HOST:-smtp.gmail.com} + EMAIL_PORT: ${EMAIL_PORT:-587} + EMAIL_HOST_USER: ${EMAIL_HOST_USER} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} + DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL:-Casera } + EMAIL_USE_TLS: "true" + + # Worker settings (UTC hours for scheduled jobs) + TASK_REMINDER_HOUR: ${TASK_REMINDER_HOUR:-14} + OVERDUE_REMINDER_HOUR: ${OVERDUE_REMINDER_HOUR:-15} + DAILY_DIGEST_HOUR: ${DAILY_DIGEST_HOUR:-3} volumes: - - ./:/app/src:ro + - ./push_certs:/certs:ro + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + networks: + - casera-network + +volumes: + postgres_data: + redis_data: + +networks: + casera-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index 963fcc4..2a7a729 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,167 +1,178 @@ +# Production / Docker Swarm compose file +# Usage: +# docker stack deploy -c docker-compose.yml casera +# +# All env vars must be set in the environment or a .env file. +# No dev-safe defaults — missing vars will fail the deploy. + services: # PostgreSQL Database db: image: postgres:16-alpine - container_name: casera-db - restart: unless-stopped environment: - POSTGRES_USER: ${POSTGRES_USER:-casera} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-casera_dev_password} - POSTGRES_DB: ${POSTGRES_DB:-casera} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} volumes: - postgres_data:/var/lib/postgresql/data - ports: - - "${DB_PORT:-5433}:5432" # Use 5433 externally to avoid conflicts + # DB port NOT exposed externally — only reachable within overlay network healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-casera} -d ${POSTGRES_DB:-casera}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 + deploy: + replicas: 1 + restart_policy: + condition: any + delay: 5s + update_config: + parallelism: 1 + delay: 10s networks: - casera-network # Redis Cache redis: image: redis:7-alpine - container_name: casera-redis - restart: unless-stopped command: redis-server --appendonly yes volumes: - redis_data:/data - ports: - - "${REDIS_PORT:-6379}:6379" + # Redis port NOT exposed externally — only reachable within overlay network healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 + deploy: + replicas: 1 + restart_policy: + condition: any + delay: 5s networks: - casera-network # Casera API api: - platform: linux/arm64 - build: - context: . - target: api - container_name: casera-api - restart: unless-stopped + image: ${REGISTRY:-ghcr.io/treytartt}/casera-api:${TAG:-latest} ports: - "${PORT:-8000}:8000" environment: # Server PORT: "8000" DEBUG: "${DEBUG:-false}" - ALLOWED_HOSTS: "${ALLOWED_HOSTS:-localhost,127.0.0.1}" + ALLOWED_HOSTS: "${ALLOWED_HOSTS}" TIMEZONE: "${TIMEZONE:-UTC}" # Database DB_HOST: db DB_PORT: "5432" - POSTGRES_USER: ${POSTGRES_USER:-casera} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-casera_dev_password} - POSTGRES_DB: ${POSTGRES_DB:-casera} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} DB_SSLMODE: "${DB_SSLMODE:-disable}" # Redis REDIS_URL: "redis://redis:6379/0" # Security - SECRET_KEY: ${SECRET_KEY:-dev-secret-key-change-in-production-min-32-chars} + SECRET_KEY: ${SECRET_KEY} # Email - EMAIL_HOST: ${EMAIL_HOST:-smtp.gmail.com} + EMAIL_HOST: ${EMAIL_HOST} EMAIL_PORT: ${EMAIL_PORT:-587} EMAIL_HOST_USER: ${EMAIL_HOST_USER} EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} - DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL:-Casera } + DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL} EMAIL_USE_TLS: "${EMAIL_USE_TLS:-true}" - # Push Notifications (Direct APNs/FCM - no Gorush) - APNS_AUTH_KEY_PATH: ${APNS_AUTH_KEY_PATH} + # Push Notifications + APNS_AUTH_KEY_PATH: "/certs/apns_key.p8" APNS_AUTH_KEY_ID: ${APNS_AUTH_KEY_ID} APNS_TEAM_ID: ${APNS_TEAM_ID} - APNS_TOPIC: ${APNS_TOPIC:-com.example.casera} - APNS_USE_SANDBOX: "${APNS_USE_SANDBOX:-true}" + APNS_TOPIC: ${APNS_TOPIC} + APNS_USE_SANDBOX: "${APNS_USE_SANDBOX:-false}" FCM_SERVER_KEY: ${FCM_SERVER_KEY} volumes: - - ./push_certs:/certs:ro - - ./uploads:/app/uploads - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy + - push_certs:/certs:ro + - uploads:/app/uploads + # TODO: migrate secrets to Docker secrets (docker secret create) healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8000/api/health/"] + test: ["CMD", "curl", "-f", "http://127.0.0.1:8000/api/health/"] interval: 30s timeout: 10s + start_period: 15s retries: 3 + deploy: + replicas: 1 + restart_policy: + condition: any + delay: 5s + update_config: + parallelism: 1 + delay: 10s + order: start-first networks: - casera-network # Casera Admin Panel (Next.js) admin: - platform: linux/arm64 - build: - context: . - target: admin - container_name: casera-admin - restart: unless-stopped + image: ${REGISTRY:-ghcr.io/treytartt}/casera-admin:${TAG:-latest} ports: - "${ADMIN_PORT:-3000}:3000" environment: PORT: "3000" HOSTNAME: "0.0.0.0" - NEXT_PUBLIC_API_URL: "${NEXT_PUBLIC_API_URL:-http://api:8000}" - depends_on: - api: - condition: service_healthy + NEXT_PUBLIC_API_URL: "${NEXT_PUBLIC_API_URL}" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/admin/"] interval: 30s timeout: 10s retries: 3 + deploy: + replicas: 1 + restart_policy: + condition: any + delay: 5s + update_config: + parallelism: 1 + delay: 10s + order: start-first networks: - casera-network # Casera Worker (Background Jobs) worker: - platform: linux/arm64 - build: - context: . - target: worker - container_name: casera-worker - restart: unless-stopped + image: ${REGISTRY:-ghcr.io/treytartt}/casera-worker:${TAG:-latest} environment: # Database DB_HOST: db DB_PORT: "5432" - POSTGRES_USER: ${POSTGRES_USER:-casera} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-casera_dev_password} - POSTGRES_DB: ${POSTGRES_DB:-casera} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} DB_SSLMODE: "${DB_SSLMODE:-disable}" # Redis REDIS_URL: "redis://redis:6379/0" # Security - SECRET_KEY: ${SECRET_KEY:-dev-secret-key-change-in-production-min-32-chars} + SECRET_KEY: ${SECRET_KEY} - # Push Notifications (Direct APNs/FCM - no Gorush) + # Push Notifications APNS_AUTH_KEY_PATH: "/certs/apns_key.p8" APNS_AUTH_KEY_ID: ${APNS_AUTH_KEY_ID} APNS_TEAM_ID: ${APNS_TEAM_ID} - APNS_TOPIC: ${APNS_TOPIC:-com.example.casera} - APNS_USE_SANDBOX: "${APNS_USE_SANDBOX:-true}" + APNS_TOPIC: ${APNS_TOPIC} + APNS_USE_SANDBOX: "${APNS_USE_SANDBOX:-false}" FCM_SERVER_KEY: ${FCM_SERVER_KEY} # Email - EMAIL_HOST: ${EMAIL_HOST:-smtp.gmail.com} + EMAIL_HOST: ${EMAIL_HOST} EMAIL_PORT: ${EMAIL_PORT:-587} EMAIL_HOST_USER: ${EMAIL_HOST_USER} EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} - DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL:-Casera } + DEFAULT_FROM_EMAIL: ${DEFAULT_FROM_EMAIL} EMAIL_USE_TLS: "${EMAIL_USE_TLS:-true}" # Worker settings (UTC hours for scheduled jobs) @@ -169,19 +180,21 @@ services: OVERDUE_REMINDER_HOUR: ${OVERDUE_REMINDER_HOUR:-15} DAILY_DIGEST_HOUR: ${DAILY_DIGEST_HOUR:-3} volumes: - - ./push_certs:/certs:ro - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy + - push_certs:/certs:ro + deploy: + replicas: 1 + restart_policy: + condition: any + delay: 5s networks: - casera-network volumes: postgres_data: redis_data: + push_certs: + uploads: networks: casera-network: - driver: bridge + driver: overlay diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index cd8889a..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,43 +0,0 @@ -# Build stage -FROM golang:1.21-alpine AS builder - -WORKDIR /app - -# Install build dependencies -RUN apk add --no-cache git ca-certificates - -# Copy go mod files -COPY go.mod go.sum ./ -RUN go mod download - -# Copy source code -COPY . . - -# Build binaries -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o mycrib-api ./cmd/api -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o mycrib-worker ./cmd/worker - -# Runtime stage -FROM alpine:3.19 - -WORKDIR /app - -# Install runtime dependencies -RUN apk add --no-cache ca-certificates tzdata - -# Copy binaries from builder -COPY --from=builder /app/mycrib-api . -COPY --from=builder /app/mycrib-worker . - -# Copy templates if needed -COPY --from=builder /app/templates ./templates - -# Create non-root user -RUN adduser -D -g '' appuser -USER appuser - -# Expose port -EXPOSE 8000 - -# Default command (API server) -CMD ["./mycrib-api"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 0c17ae6..0000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,96 +0,0 @@ -version: '3.8' - -services: - api: - build: - context: .. - dockerfile: docker/Dockerfile - ports: - - "8000:8000" - environment: - - PORT=8000 - - DEBUG=true - - SECRET_KEY=${SECRET_KEY:-development-secret-key} - - POSTGRES_DB=${POSTGRES_DB:-casera} - - POSTGRES_USER=${POSTGRES_USER:-postgres} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} - - DB_HOST=db - - DB_PORT=5432 - - REDIS_URL=redis://redis:6379/0 - - EMAIL_HOST=${EMAIL_HOST:-smtp.gmail.com} - - EMAIL_PORT=${EMAIL_PORT:-587} - - EMAIL_HOST_USER=${EMAIL_HOST_USER:-} - - EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD:-} - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - restart: unless-stopped - networks: - - casera-network - - worker: - build: - context: .. - dockerfile: docker/Dockerfile - command: ["./casera-worker"] - environment: - - DEBUG=true - - SECRET_KEY=${SECRET_KEY:-development-secret-key} - - POSTGRES_DB=${POSTGRES_DB:-casera} - - POSTGRES_USER=${POSTGRES_USER:-postgres} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} - - DB_HOST=db - - DB_PORT=5432 - - REDIS_URL=redis://redis:6379/0 - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - restart: unless-stopped - networks: - - casera-network - - db: - image: postgres:15-alpine - environment: - - POSTGRES_DB=${POSTGRES_DB:-casera} - - POSTGRES_USER=${POSTGRES_USER:-postgres} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} - volumes: - - postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-casera}"] - interval: 5s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - casera-network - - redis: - image: redis:7-alpine - ports: - - "6379:6379" - volumes: - - redis_data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - timeout: 5s - retries: 5 - restart: unless-stopped - networks: - - casera-network - -volumes: - postgres_data: - redis_data: - -networks: - casera-network: - driver: bridge