Files
demo-decoupled/Dockerfile
Liosha84andClaude Opus 5.5 514e1fd0c7 feat: finish demo — port demo-monolith cleanup, separate UI and API containers
Frontend
- Bring spring-angular-ui back under version control with the Angular 22 +
  Material UI from demo-monolith; stop committing the built static/ folder
- Serve the UI from nginx, which proxies /api to the backend so the JWT
  cookie works on a single origin, and falls back to index.html for routes

Backend
- Add PUT /api/users/{id}/roles (admin only) that keeps at least one admin
  and does not let an admin drop their own admin role
- Tighten security: read tutorials publicly, change them only when logged
  in, users/roles API for admins only; hide password hashes in JSON
- Remove unused Settings and Test endpoints, DTO mappers, commented code
- Flyway V2 aligns the schema with the entities; Hibernate now runs with
  ddl-auto: validate
- JWT lifetime matches the 24h cookie; fix the cookie name typo
- Keep CORS only for calling the API directly (ng serve without proxy)

Docker
- Backend Dockerfile: Gradle build + JRE runtime with layer caching,
  non-root user and healthcheck; add .dockerignore
- UI Dockerfile: Node 24 build + nginx with healthcheck
- docker-compose runs its own Postgres, starts services in health order
  and always rebuilds images

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:33:38 +02:00

53 lines
2.1 KiB
Docker

# syntax=docker/dockerfile:1
# Образ только бэкенда (REST API); фронт собирается отдельно, см. spring-angular-ui/Dockerfile
# 1. Сборка jar
FROM eclipse-temurin:26-jdk-jammy AS build
WORKDIR /app
# Сначала Gradle-обвязка — зависимости скачиваются заново лишь при изменении build.gradle
COPY gradlew settings.gradle build.gradle ./
COPY gradle ./gradle
RUN chmod +x gradlew
RUN --mount=type=cache,target=/root/.gradle \
./gradlew dependencies --no-daemon -q > /dev/null
COPY src ./src
RUN --mount=type=cache,target=/root/.gradle \
./gradlew bootJar -x test --no-daemon \
&& cp "$(ls build/libs/*.jar | grep -v -- '-plain.jar' | head -n 1)" app.jar
# Раскладываем jar по слоям: зависимости меняются редко и кэшируются отдельно от кода
RUN java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extracted
# 2. Запуск
FROM eclipse-temurin:26-jre-jammy
# curl нужен для healthcheck
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
# Непривилегированный пользователь; InitDataService пишет начальные данные в /jambotron_data
RUN groupadd --system app \
&& useradd --system --gid app --home-dir /app app \
&& mkdir -p /jambotron_data \
&& chown app:app /jambotron_data
WORKDIR /app
COPY --from=build --chown=app:app /app/extracted/dependencies/ ./
COPY --from=build --chown=app:app /app/extracted/spring-boot-loader/ ./
COPY --from=build --chown=app:app /app/extracted/snapshot-dependencies/ ./
COPY --from=build --chown=app:app /app/extracted/application/ ./
USER app
EXPOSE 8080
HEALTHCHECK --interval=15s --timeout=5s --start-period=60s --retries=5 \
CMD curl -fs http://localhost:8080/actuator/health || exit 1
# JVM ориентируется на лимит памяти контейнера, а не на память хоста
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "org.springframework.boot.loader.launch.JarLauncher"]