You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker化React调用FastAPI:为何localhost可行而服务名触发CORS?

Docker容器通信与CORS问题排查

问题场景

我有两个处于同一Docker网络的容器:Docker化React应用(使用Vite开发服务器)和Docker化FastAPI后端,docker-compose.yml配置如下:

services:
  backend:
    build:
      context: ./backend
    ports: 
      - "8000:8000"
    volumes:
      - ./backend/app:/code/app
    networks:
      - app-network

  react-frontend:
    build: 
      context: ./react-frontend 
      dockerfile: Dockerfile
    restart: always
    environment:
      - VITE_DEV=true
    ports:
      - "3000:3000"
    volumes:
      - ./react-frontend:/app
      - /app/node_modules
    command: npm run dev
    networks:
      - app-network
    depends_on:
      - backend

networks:
  app-network:
    driver: bridge

FastAPI已配置CORS允许来源:

origins = [
    "http://react-frontend:3000",
    "http://localhost:3000"
]

middleware = [
    Middleware(
        CORSMiddleware,
        allow_origins=origins,
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"]
    )
]

app = FastAPI(lifespan=lifespan, middleware=middleware)

尝试用Docker服务名http://backend请求后端时触发CORS错误:

const URL = "http://backend/"  // 触发CORS错误
try {
    const response = await axios.get(URL)
    console.log(response.data)
} catch (error) {
    console.error("An error occurred")
}

错误信息:

跨源请求被阻止:同源策略禁止读取http://backend/的外部资源。原因:CORS请求失败。状态码:(null)。

但使用http://localhost:8000请求却正常工作:

const URL = "http://localhost:8000/" // 正常工作
try {
    const response = await axios.get(URL)
    console.log(response.data)
} catch (error) {
    console.error("An error occurred")
}

疑问:按Docker网络逻辑,容器应通过服务名通信,localhost不应生效,为何实际相反?是否需修改vite.config.js来适配服务名调用?


原因分析

  • 请求发起方是浏览器而非React容器:Vite开发服务器运行在React容器中,但前端代码最终是在本地浏览器里执行的。浏览器不在Docker网络范围内,无法解析backend这个服务名,请求根本无法到达后端,进而触发浏览器的同源策略拦截(表现为CORS错误)。
  • localhost能生效的核心原因:docker-compose配置里把后端容器的8000端口映射到了宿主机的8000端口,浏览器访问localhost:8000时,实际是通过宿主机的端口转发机制连接到后端容器。同时这个请求的来源是http://localhost:3000,符合FastAPI的CORS允许列表,所以能正常响应。

解决方法:配置Vite代理

要在开发环境下让前端通过Docker服务名访问后端,需要借助Vite的代理功能,把前端请求转发到Docker网络内的后端服务。修改vite.config.js:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      // 匹配所有以/api开头的请求
      '/api': {
        target: 'http://backend:8000', // Docker服务名+后端容器端口
        changeOrigin: true,
        // 如果后端接口没有/api前缀,可通过rewrite去掉请求里的/api
        // rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

然后修改前端请求代码,使用相对路径:

// 假设后端接口路径为/api/xxx
const response = await axios.get('/api/')

配置后逻辑:

  1. 浏览器向Vite开发服务器发起请求,二者同源(均为localhost:3000),不会触发浏览器的同源策略拦截。
  2. Vite服务器作为代理,将请求转发到Docker网络内的backend:8000,服务器间通信不受浏览器同源策略限制。

内容的提问来源于stack exchange,提问作者OhDaeSu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 04:12:10