diff --git a/.gitignore b/.gitignore
index cf91eba..293d1d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,7 @@ target/
# Local service configuration (copy the committed .sample to application.yml)
patbond-*/src/main/resources/application.yml
!patbond-*/src/main/resources/application.yml.sample
+
+# Docker compose 本地机密(deploy/init-secrets.sh 生成,绝不入库)
+.env
+deploy/keys/
diff --git a/Readme.md b/Readme.md
index e886138..2d30a1f 100644
--- a/Readme.md
+++ b/Readme.md
@@ -103,6 +103,34 @@ curl -X POST http://127.0.0.1:8081/api/v1/auth/register \
Machine-specific values live in the git-ignored `application.yml` (copied from the
committed `.sample`); never commit secrets to the samples.
+## Docker Compose
+
+MVP 编排(ADR-007):`postgres:18`(数据落 volume)+ 两个无状态应用容器。
+配置与本机运行同一套约定 —— 容器内挂载 `application.yml.sample` 作为配置,
+`PATBOND_*` 环境变量注入实际值。
+
+```bash
+# 1. 生成 RS256 密钥对与 .env(DB 口令、内部令牌;产物被 .gitignore 忽略)
+./deploy/init-secrets.sh
+
+# 2. 构建可执行 jar
+JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -DskipTests package
+
+# 3. 启动(首次会构建镜像)
+docker compose up -d --build
+
+# 冒烟
+curl -s -X POST http://127.0.0.1:8081/api/v1/auth/register \
+ -H 'Content-Type: application/json' \
+ -d '{"username":"demo_user","password":"secret123"}'
+
+# 停止(-v 同时删除数据库数据)
+docker compose down
+```
+
+注意:数据库端口不对宿主机发布;`8082`(user)在 MVP 阶段直连暴露以提供
+`/api/v1/me`,`/internal/**` 由服务间令牌保护,规模化阶段应改由网关统一入口。
+
## Services
### Auth Service
diff --git a/deploy/init-secrets.sh b/deploy/init-secrets.sh
new file mode 100755
index 0000000..12cf051
--- /dev/null
+++ b/deploy/init-secrets.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# 生成 docker compose 运行所需的本地机密:RS256 密钥对 + .env(DB 口令、内部令牌)。
+# 产物全部被 .gitignore 忽略,绝不入库;重复执行是幂等的(已存在则不覆盖)。
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+mkdir -p deploy/keys
+if [ ! -f deploy/keys/jwt-private.pem ]; then
+ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out deploy/keys/jwt-private.pem
+ echo "已生成 deploy/keys/jwt-private.pem"
+fi
+openssl pkey -in deploy/keys/jwt-private.pem -pubout -out deploy/keys/jwt-public.pem
+echo "已生成 deploy/keys/jwt-public.pem"
+
+if [ ! -f .env ]; then
+ {
+ echo "PATBOND_DB_PASSWORD=$(openssl rand -hex 16)"
+ echo "PATBOND_INTERNAL_TOKEN=$(openssl rand -hex 32)"
+ } > .env
+ echo "已生成 .env(随机 DB 口令与内部令牌)"
+fi
+
+# 容器内以 uid 10001 运行,密钥需可读
+chmod 644 deploy/keys/jwt-public.pem deploy/keys/jwt-private.pem
+echo "OK:deploy/keys/ 与 .env 就绪(均已被 .gitignore 忽略)"
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..741aedc
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,64 @@
+# Patbond MVP 编排(ADR-007):应用容器无状态,PostgreSQL 数据落 volume。
+# 使用步骤见 Readme.md「Docker Compose」一节:
+# 1) ./deploy/init-secrets.sh 生成 RS256 密钥对与 .env(均不入库)
+# 2) JAVA_HOME=... ./mvnw -DskipTests package
+# 3) docker compose up -d --build
+#
+# 配置来源:容器内挂载各服务的 application.yml.sample 作为配置文件,
+# 其中的 ${PATBOND_*} 占位由下方 environment 注入 —— 与本机运行同一套约定。
+name: patbond
+
+services:
+ postgres:
+ image: postgres:18
+ environment:
+ POSTGRES_DB: ${PATBOND_DB_NAME:-patbond}
+ POSTGRES_USER: ${PATBOND_DB_USER:-patbond}
+ POSTGRES_PASSWORD: ${PATBOND_DB_PASSWORD:?先运行 deploy/init-secrets.sh 生成 .env}
+ volumes:
+ # postgres:18 官方镜像的挂载点是 /var/lib/postgresql(含版本子目录)
+ - pgdata:/var/lib/postgresql
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${PATBOND_DB_USER:-patbond} -d ${PATBOND_DB_NAME:-patbond}"]
+ interval: 2s
+ timeout: 3s
+ retries: 30
+ # 数据库不对宿主机发布端口;调试需要时可临时加 ports: ["15432:5432"]
+
+ user:
+ build: ./patbond-user
+ environment:
+ SPRING_CONFIG_LOCATION: file:/config/application.yml
+ PATBOND_DB_URL: jdbc:postgresql://postgres:5432/${PATBOND_DB_NAME:-patbond}
+ PATBOND_DB_USER: ${PATBOND_DB_USER:-patbond}
+ PATBOND_DB_PASSWORD: ${PATBOND_DB_PASSWORD:?先运行 deploy/init-secrets.sh 生成 .env}
+ PATBOND_INTERNAL_TOKEN: ${PATBOND_INTERNAL_TOKEN:?先运行 deploy/init-secrets.sh 生成 .env}
+ PATBOND_JWT_PUBLIC_KEY: /run/patbond/keys/jwt-public.pem
+ volumes:
+ - ./patbond-user/src/main/resources/application.yml.sample:/config/application.yml:ro
+ - ./deploy/keys:/run/patbond/keys:ro
+ # MVP 直连暴露 8082 供客户端访问 /api/v1/me;/internal/** 已有服务间鉴权,
+ # 规模化阶段应由网关统一入口并停止直接暴露本端口。
+ ports:
+ - "${PATBOND_USER_PORT:-8082}:8082"
+ depends_on:
+ postgres:
+ condition: service_healthy
+
+ auth:
+ build: ./patbond-auth
+ environment:
+ SPRING_CONFIG_LOCATION: file:/config/application.yml
+ PATBOND_USER_SERVICE_URL: http://user:8082
+ PATBOND_INTERNAL_TOKEN: ${PATBOND_INTERNAL_TOKEN:?先运行 deploy/init-secrets.sh 生成 .env}
+ PATBOND_JWT_PRIVATE_KEY: /run/patbond/keys/jwt-private.pem
+ volumes:
+ - ./patbond-auth/src/main/resources/application.yml.sample:/config/application.yml:ro
+ - ./deploy/keys:/run/patbond/keys:ro
+ ports:
+ - "${PATBOND_AUTH_PORT:-8081}:8081"
+ depends_on:
+ - user
+
+volumes:
+ pgdata:
diff --git a/patbond-auth/Dockerfile b/patbond-auth/Dockerfile
new file mode 100644
index 0000000..59559e8
--- /dev/null
+++ b/patbond-auth/Dockerfile
@@ -0,0 +1,9 @@
+# Runtime image only — build the jar first: ./mvnw -pl patbond-auth -am package
+# Stateless by design (ADR-007): no local state, config via env / mounted files.
+FROM eclipse-temurin:17-jre
+RUN useradd --system --uid 10001 patbond
+USER patbond
+WORKDIR /app
+COPY target/patbond-auth-1.0.0-SNAPSHOT-exec.jar app.jar
+EXPOSE 8081
+ENTRYPOINT ["java", "-jar", "/app/app.jar"]
diff --git a/patbond-auth/pom.xml b/patbond-auth/pom.xml
index 370769a..2b0cf5b 100644
--- a/patbond-auth/pom.xml
+++ b/patbond-auth/pom.xml
@@ -88,6 +88,22 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+
+ repackage
+
+
+
+ exec
+
+
+
diff --git a/patbond-user/Dockerfile b/patbond-user/Dockerfile
new file mode 100644
index 0000000..e53bdb8
--- /dev/null
+++ b/patbond-user/Dockerfile
@@ -0,0 +1,9 @@
+# Runtime image only — build the jar first: ./mvnw -pl patbond-user -am package
+# Stateless by design (ADR-007): no local state, config via env / mounted files.
+FROM eclipse-temurin:17-jre
+RUN useradd --system --uid 10001 patbond
+USER patbond
+WORKDIR /app
+COPY target/patbond-user-1.0.0-SNAPSHOT-exec.jar app.jar
+EXPOSE 8082
+ENTRYPOINT ["java", "-jar", "/app/app.jar"]
diff --git a/patbond-user/pom.xml b/patbond-user/pom.xml
index 8fb5158..475217d 100644
--- a/patbond-user/pom.xml
+++ b/patbond-user/pom.xml
@@ -98,6 +98,22 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+
+ repackage
+
+
+
+ exec
+
+
+