diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3518a23 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.git +.gitignore +.idea +.vscode + +.env +.env.* +!.env.example + +frontend/node_modules +frontend/dist +frontend/.vite + +backend/target + +**/*.log +.DS_Store diff --git a/.mise.toml b/.mise.toml index 47fa936..37d8fdc 100644 --- a/.mise.toml +++ b/.mise.toml @@ -30,6 +30,13 @@ lefthook install cp -n .env.example .env 2>/dev/null || true """ +[tasks.maven-shell] +description = "Open an interactive shell with mise Maven/Java on PATH" +run = """ +#!/usr/bin/env bash +exec "${SHELL:-/bin/bash}" -i +""" + [tasks.backend] description = "Run the Spring Boot backend" run = "backend/mvnw -f backend/pom.xml spring-boot:run" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ce1ac83 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# syntax=docker/dockerfile:1.7 + +FROM node:22-alpine AS frontend-build +WORKDIR /app/frontend + +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci + +COPY frontend/ ./ +RUN npm run build + + +FROM maven:3.9.9-eclipse-temurin-21 AS backend-build +WORKDIR /app + +COPY backend/pom.xml backend/ +RUN --mount=type=cache,target=/root/.m2 mvn -f backend/pom.xml -q -DskipTests dependency:go-offline + +COPY backend/ backend/ +COPY --from=frontend-build /app/frontend/dist/ backend/src/main/resources/static/ +RUN --mount=type=cache,target=/root/.m2 mvn -f backend/pom.xml -q -DskipTests package spring-boot:repackage + + +FROM eclipse-temurin:21-jre-alpine AS runtime +WORKDIR /app + +COPY --from=backend-build /app/backend/target/app-0.0.1-SNAPSHOT.jar app.jar + +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/README.md b/README.md index 69cd535..31e670c 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,18 @@ mise run frontend If you have activated mise in your shell, the direct commands also work: `backend/mvnw -f backend/pom.xml spring-boot:run` and `cd frontend && npm run dev`. +## Docker (all-in-one app + database) + +Build and run the bundled app container plus PostgreSQL: + +```bash +docker compose -f docker-compose.full.yml up --build +``` + +- App: http://localhost:8080 +- API: http://localhost:8080/api +- Database: postgres://app:app@localhost:5432/app_dev + ## Verify everything ```bash diff --git a/backend/src/main/java/com/example/app/common/SpaForwardController.java b/backend/src/main/java/com/example/app/common/SpaForwardController.java new file mode 100644 index 0000000..a880bc7 --- /dev/null +++ b/backend/src/main/java/com/example/app/common/SpaForwardController.java @@ -0,0 +1,12 @@ +package com.example.app.common; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class SpaForwardController { + @GetMapping({"/weekly", "/monthly", "/projects"}) + public String forwardToIndex() { + return "forward:/index.html"; + } +} diff --git a/docker-compose.full.yml b/docker-compose.full.yml new file mode 100644 index 0000000..54d85ce --- /dev/null +++ b/docker-compose.full.yml @@ -0,0 +1,34 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" + environment: + DATABASE_URL: jdbc:postgresql://db:5432/app_dev + DATABASE_USERNAME: app + DATABASE_PASSWORD: app + LOG_LEVEL: INFO + depends_on: + db: + condition: service_healthy + + db: + image: postgres:17-alpine + ports: + - "5432:5432" + environment: + POSTGRES_DB: app_dev + POSTGRES_USER: app + POSTGRES_PASSWORD: app + healthcheck: + test: ["CMD-SHELL", "pg_isready -U app -d app_dev"] + interval: 5s + timeout: 5s + retries: 10 + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: