首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >使用 Docker Compose 构建全栈应用(React + Flask + PostgreSQL)

使用 Docker Compose 构建全栈应用(React + Flask + PostgreSQL)

原创
作者头像
资源大佬 jzit-top
发布2026-07-30 16:54:08
发布2026-07-30 16:54:08
940
举报

使用 Docker Compose 构建全栈应用(React + Flask + PostgreSQL)

作者:Ethan Zhang | 发布时间:2026-07-30 | 标签:Docker React Flask PostgreSQL 全栈



1. 为什么选择 Docker Compose?

传统开发中,前后端、数据库往往需要分别安装配置环境,团队协作时极易出现“在我机器上能跑”的问题。Docker Compose 通过一个 YAML 文件定义多容器应用,实现:

  • 环境一致性:开发、测试、生产使用相同镜像
  • 依赖隔离:每个服务运行在独立的容器中
  • 快速启动:一条命令拉起所有服务
  • 可扩展性:轻松增加服务副本或添加新组件

本文将带你亲手搭建一个生产级模板,掌握容器化全栈开发的精髓。


2. 项目整体架构

代码语言:javascript
复制
fullstack-app/
├── backend/                  # Flask 后端
│   ├── Dockerfile
│   ├── requirements.txt
│   ├── app.py
│   └── models.py
├── frontend/                 # React 前端
│   ├── Dockerfile
│   ├── package.json
│   ├── vite.config.js
│   ├── index.html
│   └── src/
│       ├── App.jsx
│       ├── main.jsx
│       └── api.js
├── docker-compose.yml
└── .env                      # 环境变量(不提交)
  • backend:提供 RESTful API,连接 PostgreSQL,处理业务逻辑。
  • frontend:基于 Vite + React,展示数据并调用后端 API。
  • postgres:官方 PostgreSQL 镜像,持久化数据。
  • nginx(可选):生产环境可加反向代理,本文暂略。

3. 后端服务(Flask API)

3.1 项目文件

backend/requirements.txt

代码语言:javascript
复制
Flask==2.3.3
Flask-CORS==4.0.0
psycopg2-binary==2.9.9
python-dotenv==1.0.0

backend/models.py(数据库模型)

代码语言:javascript
复制
import os
import psycopg2
from psycopg2.extras import RealDictCursor

def get_db_connection():
    conn = psycopg2.connect(
        host=os.environ.get('DB_HOST', 'db'),
        database=os.environ.get('DB_NAME', 'postgres'),
        user=os.environ.get('DB_USER', 'postgres'),
        password=os.environ.get('DB_PASSWORD', 'postgres'),
        port=5432
    )
    return conn

def init_db():
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute('''
        CREATE TABLE IF NOT EXISTS items (
            id SERIAL PRIMARY KEY,
            name VARCHAR(100) NOT NULL,
            description TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    conn.commit()
    cur.close()
    conn.close()

def get_all_items():
    conn = get_db_connection()
    cur = conn.cursor(cursor_factory=RealDictCursor)
    cur.execute('SELECT * FROM items ORDER BY id')
    items = cur.fetchall()
    cur.close()
    conn.close()
    return items

def add_item(name, description):
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute(
        'INSERT INTO items (name, description) VALUES (%s, %s) RETURNING id',
        (name, description)
    )
    item_id = cur.fetchone()[0]
    conn.commit()
    cur.close()
    conn.close()
    return item_id

backend/app.py(主入口)

代码语言:javascript
复制
import os
from flask import Flask, request, jsonify
from flask_cors import CORS
from models import init_db, get_all_items, add_item

app = Flask(__name__)
CORS(app)  # 允许前端跨域

@app.route('/health', methods=['GET'])
def health():
    return jsonify({'status': 'ok'})

@app.route('/api/items', methods=['GET'])
def list_items():
    items = get_all_items()
    return jsonify(items)

@app.route('/api/items', methods=['POST'])
def create_item():
    data = request.get_json()
    if not data or 'name' not in data:
        return jsonify({'error': 'Name is required'}), 400
    name = data['name']
    description = data.get('description', '')
    item_id = add_item(name, description)
    return jsonify({'id': item_id, 'name': name, 'description': description}), 201

if __name__ == '__main__':
    init_db()
    app.run(host='0.0.0.0', port=5000, debug=True)

backend/Dockerfile

代码语言:javascript
复制
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENV FLASK_ENV=development
EXPOSE 5000

CMD ["python", "app.py"]

4. 前端服务(React + Vite)

4.1 初始化项目

使用 Vite 快速创建 React 项目(TypeScript 可选,本文用 JSX)。

frontend/package.json(核心依赖)

代码语言:javascript
复制
{
  "name": "frontend",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite --host 0.0.0.0",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "axios": "^1.6.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.0.0",
    "vite": "^4.5.0"
  }
}

frontend/vite.config.js

代码语言:javascript
复制
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://backend:5000',
        changeOrigin: true,
      }
    }
  }
})

frontend/src/api.js(API 调用)

代码语言:javascript
复制
import axios from 'axios';

const api = axios.create({
  baseURL: '/api',  // 开发时通过 Vite 代理转发到后端
  headers: { 'Content-Type': 'application/json' }
});

export const getItems = () => api.get('/items');
export const createItem = (name, description) => api.post('/items', { name, description });

frontend/src/App.jsx

代码语言:javascript
复制
import React, { useState, useEffect } from 'react';
import { getItems, createItem } from './api';

function App() {
  const [items, setItems] = useState([]);
  const [name, setName] = useState('');
  const [desc, setDesc] = useState('');

  const loadItems = async () => {
    try {
      const res = await getItems();
      setItems(res.data);
    } catch (err) {
      console.error('加载数据失败', err);
    }
  };

  useEffect(() => {
    loadItems();
  }, []);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!name.trim()) return;
    await createItem(name, desc);
    setName('');
    setDesc('');
    loadItems();
  };

  return (
    <div style={{ padding: '2rem', fontFamily: 'sans-serif' }}>
      <h1>📦 物品列表</h1>
      <form onSubmit={handleSubmit} style={{ marginBottom: '1.5rem' }}>
        <input
          type="text"
          placeholder="名称"
          value={name}
          onChange={(e) => setName(e.target.value)}
          style={{ marginRight: '0.5rem' }}
        />
        <input
          type="text"
          placeholder="描述"
          value={desc}
          onChange={(e) => setDesc(e.target.value)}
          style={{ marginRight: '0.5rem' }}
        />
        <button type="submit">添加</button>
      </form>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <strong>{item.name}</strong> - {item.description || '无描述'}
            <span style={{ marginLeft: '1rem', color: '#888', fontSize: '0.8rem' }}>
              {new Date(item.created_at).toLocaleString()}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

frontend/src/main.jsx

代码语言:javascript
复制
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

frontend/index.html

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>全栈演示</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

frontend/Dockerfile

代码语言:javascript
复制
FROM node:20-slim AS build

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

注意:为了开发时方便热重载,我们也可以直接使用 node 镜像运行 npm run dev,但生产环境建议使用 Nginx 托管静态文件。本文采用开发模式,即直接运行 dev 服务,便于实时修改。

修改 frontend/Dockerfile 用于开发(我们采用开发模式,所以用下面这个):

代码语言:javascript
复制
FROM node:20-slim

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

EXPOSE 3000
CMD ["npm", "run", "dev"]

5. 数据库服务(PostgreSQL)

直接使用官方镜像 postgres:15-alpine,无需额外 Dockerfile。我们只需在 docker-compose.yml 中配置环境变量和持久化卷。


6. Docker Compose 编排

docker-compose.yml

代码语言:javascript
复制
version: '3.8'

services:
  db:
    image: postgres:15-alpine
    container_name: fullstack-db
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  backend:
    build: ./backend
    container_name: fullstack-backend
    environment:
      DB_HOST: db
      DB_NAME: postgres
      DB_USER: postgres
      DB_PASSWORD: postgres
    ports:
      - "5000:5000"
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app-network
    volumes:
      - ./backend:/app   # 开发时挂载代码,实现热更新
    command: python app.py

  frontend:
    build: ./frontend
    container_name: fullstack-frontend
    ports:
      - "3000:3000"
    depends_on:
      - backend
    networks:
      - app-network
    volumes:
      - ./frontend:/app
      - /app/node_modules   # 避免覆盖容器内的 node_modules
    environment:
      - VITE_API_URL=http://backend:5000   # 传递给 Vite 环境变量(可选)
    command: npm run dev

volumes:
  pgdata:

networks:
  app-network:
    driver: bridge

关键点说明

  • depends_on 确保数据库先启动,且 condition: service_healthy 保证数据库就绪后再启动后端。
  • 通过 volumes 挂载源码,修改代码后容器内自动更新(Flask 的 debug 模式、Vite 的 HMR 都支持)。
  • 网络 app-network 让容器间通过服务名(如 dbbackend)互相访问。

7. 启动与测试

7.1 启动所有服务

在项目根目录执行:

代码语言:javascript
复制
docker-compose up --build

首次启动会构建镜像并下载依赖,耐心等待。看到以下日志表示成功:

  • db 健康检查通过
  • backend 输出 Running on http://0.0.0.0:5000
  • frontend 输出 VITE v4.5.0 ready in xxx ms

7.2 访问应用

  • 前端页面:http://localhost:3000
  • 后端 API 测试:curl http://localhost:5000/health 返回 {"status":"ok"}

7.3 功能测试

在网页中添加几个物品,数据会存入 PostgreSQL,刷新页面数据保留。

7.4 查看数据库

进入数据库容器:

代码语言:javascript
复制
docker exec -it fullstack-db psql -U postgres

查询数据:

代码语言:javascript
复制
SELECT * FROM items;

8. 进阶优化

8.1 环境变量管理

将敏感信息(如数据库密码)放在 .env 文件中,并在 compose 中引用:

代码语言:javascript
复制
environment:
  POSTGRES_PASSWORD: ${DB_PASSWORD}

同时将 .env 加入 .gitignore

8.2 生产环境构建

  • 前端使用多阶段构建(如上述带 Nginx 的 Dockerfile)。
  • 后端使用 gunicorn 替代 flask run
  • 添加 Nginx 反向代理,统一入口,处理静态资源和 API 路由。

8.3 日志与监控

集成 ELK 或 Prometheus,使用 docker-compose 的 logging 驱动。

8.4 CI/CD

配合 GitHub Actions 或 GitLab CI,实现自动化构建和部署。


9. 总结与展望

本文通过一个完整的全栈示例,展示了 Docker Compose 在开发环境中的强大能力。你将以下技能收入囊中:

  • ✅ 编写 Dockerfile 构建 Python 和 Node 应用
  • ✅ 使用 docker-compose.yml 定义多服务依赖
  • ✅ 实现容器间网络通信和数据持久化
  • ✅ 开发时热重载提升效率
  • ✅ 一键启动/停止所有服务

这套模板可以直接作为你下一个项目的起点,无论你是独立开发者还是团队协作,容器化都将极大降低环境配置成本。未来你可以继续集成 Redis、消息队列或微服务网关,让架构更加健壮。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 使用 Docker Compose 构建全栈应用(React + Flask + PostgreSQL)
    • 1. 为什么选择 Docker Compose?
    • 2. 项目整体架构
    • 3. 后端服务(Flask API)
      • 3.1 项目文件
    • 4. 前端服务(React + Vite)
      • 4.1 初始化项目
    • 5. 数据库服务(PostgreSQL)
    • 6. Docker Compose 编排
    • 7. 启动与测试
      • 7.1 启动所有服务
      • 7.2 访问应用
      • 7.3 功能测试
      • 7.4 查看数据库
    • 8. 进阶优化
      • 8.1 环境变量管理
      • 8.2 生产环境构建
      • 8.3 日志与监控
      • 8.4 CI/CD
    • 9. 总结与展望
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档