对于全栈特别是和AI挂边的同学来说一定深有体会,一个项目里Java做业务、Python做AI/RAG、Vue做前端,配置环境能折腾死人。今天我们就来看看多语言栈Docker化,从本地一键启动到生成镜像。为什么要做Docker化呢?因为从技术栈来看,每个人的电脑测试、生成环境都不一样,依赖版本JDK、Python、Node很容易出问题。使用Docker统一Java/python/node环境,再也不用说“我本地是好的”废话不说,直接以例子来看看本地一键启动Docker Compose的方案:Postgres+Redis+Elasticsearch+springboot+FastAPI+Vue,一条命令全部跑起来。# 构建FROM maven:3.9-eclipse-temurin-21 AS builderWORKDIR /workspace/legalconnectCOPY legalconnect/mvnw mvnw.cmd ./COPY legalconnect/.mvn ./.mvnCOPY legalconnect/pom.xml ./RUN ./mvnw dependency:go-offline -BCOPY legalconnect/src ./srcRUN ./mvnw clean package -DskipTests -B# 运行阶段FROM eclipse-temurin:21-jre-alpineWORKDIR /appRUN addgroup -g 1001 -S spring && adduser -S spring -u 1001 \ && mkdir -p /app/logs && chown -R spring:spring /appCOPY --from=builder /workspace/legalconnect/target/*.jar /app/app.jarRUN chown spring:spring /app/app.jarUSER springENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+UseContainerSupport"EXPOSE 8080ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
FROM python:3.12-slimWORKDIR /appCOPY requirements.txt .RUN pip install --no-cache-dir --upgrade pip \ && pip install --no-cache-dir -r requirements.txtCOPY . .EXPOSE 8000CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# 构建FROM node:22-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .ARG VITE_API_BASE_URLARG VITE_AI_CHAT_BASE_URLENV VITE_API_BASE_URL=${VITE_API_BASE_URL}ENV VITE_AI_CHAT_BASE_URL=${VITE_AI_CHAT_BASE_URL}RUN npm run build# 运行FROM node:22-alpineWORKDIR /appENV NODE_ENV=productionRUN npm i -g serve@14COPY --from=builder /app/dist /appENV PORT=5173EXPOSE 5173CMD ["sh","-c","serve -s /app -l tcp://0.0.0.0:${PORT}"]
然后是最关键的docker-compose.ymlservices: postgres: image: postgres:17.5 environment: - POSTGRES_DB=${POSTGRES_DB:-legalconnect} - POSTGRES_USER=${POSTGRES_USER:-root} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} ports: ["5432:5432"] volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-root} -d ${POSTGRES_DB:-legalconnect}"] redis: image: redis:7 command: redis-server --appendonly yes ports: ["6379:379"] healthcheck: test: ["CMD", "redis-cli", "ping"] elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:9.1.1 environment: - discovery.type=single-node - xpack.security.enabled=false - ES_JAVA_OPTS=-Xms512m -Xmx512m ports: ["9200:9200"] backend: build: { context: ./backend, dockerfile: Dockerfile } environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/${POSTGRES_DB:-legalconnect} - SPRING_DATASOURCE_USERNAME=${POSTGRES_USER:-root} - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD} - SPRING_DATA_REDIS_HOST=redis - SPRING_DATA_REDIS_PORT=379 - SPRING_ELASTICSEARCH_URIS=http://elasticsearch:9200 - SPRING_CUSTOM_SECURITY_JWTSECRET=${JWT_SECRET} ports: ["8080:8080"] depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } elasticsearch: { condition: service_healthy } backend-ai: build: { context: ./backend-ai, dockerfile: Dockerfile } environment: - DATABASE_URL=postgresql://${POSTGRES_USER:-root}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-legalconnect} - REDIS_URL=redis://redis:379/0 - QDRANT_URL=${QDRANT_URL} - GOOGLE_API_KEY=${GOOGLE_API_KEY} - JWT_SECRET_KEY=${JWT_SECRET} ports: ["8000:8000"] depends_on: postgres: { condition: service_healthy } redis: { condition: service_healthy } frontend: build: context: ./frontend dockerfile: Dockerfile args: - VITE_API_BASE_URL=${VITE_API_BASE_URL:-http://localhost:8080/v1} - VITE_AI_CHAT_BASE_URL=${VITE_AI_CHAT_BASE_URL:-http://localhost:8000/api/v1} ports: ["5173:5173"]volumes: postgres_data:
这套Compose是先启动数据库、中间件、健康检查通过后再启动应用,容器内部用服务名互相访问,端口只暴露需要的,内部网络安全隔离。所有的配置从.env来。当然在实际开发中,我们经常只改后端或者只改AI服务,不用全跑,这也是没问题的。services: db: image: postgres:17.5 environment: - POSTGRES_DB=${POSTGRES_DB:-legalconnect} - POSTGRES_USER=${POSTGRES_USER:-root} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} ports: ["5432:5432"] redis: image: redis:7 backend: image: maven:3.9-eclipse-temurin-21 working_dir: /workspace/backend/legalconnect command: ./mvnw spring-boot:run environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/${POSTGRES_DB:-legalconnect} - SPRING_DATASOURCE_USERNAME=${POSTGRES_USER:-root} - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD} - SPRING_DATA_REDIS_HOST=redis - SPRING_DATA_REDIS_PORT=379 volumes: - ./legalconnect:/workspace/backend/legalconnect - ~/.m2:/root/.m2 ports: ["8080:8080"]
services: db: image: postgres:17.5 environment: - POSTGRES_DB=${POSTGRES_DB:-legal_connect_db} - POSTGRES_USER=${POSTGRES_USER:-legal} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} ports: ["5432:5432"] redis: image: redis:7 fastapi-app: build: { context: ., dockerfile: Dockerfile } environment: - DATABASE_URL=postgresql://${POSTGRES_USER:-legal}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-legal_connect_db} - REDIS_URL=redis://redis:379/0 ports: ["8000:8000"]
非常的方便,新建.env 把密码、密钥放进去,检查端口是否被占用,然后启动docker compose up -d --build
docker compose logs -f backend backend-ai frontend
docker compose downdocker compose down -v # 清空数据库卷
当然在这个过程中也要注意以下的问题,端口冲突,一定要关闭本地PostgresSQL、Redis等,结构改了一定要docker compose down -v;避免环境变量缺失;容器内不要用localhost,要用服务名,还有就是文件权限问题,非root哦那个胡要正确的给目录赋权。