Docker化Next.js13+Go应用登录遇服务端异常及ECONNREFUSED错误
问题:Docker化Next.js+Go应用后登录功能异常
- Firefox浏览器显示服务端异常,控制台出现
XHRGET http://127.0.0.1:3000/logout?_rsc=acgkz404错误; - Docker容器日志显示前端请求
http://127.0.0.1:8080时触发connect ECONNREFUSED 127.0.0.1:8080错误; - 本地非Docker环境登录正常,Docker环境下前端
http://127.0.0.1:3000/login和后端http://127.0.0.1:8080均可访问,且前端容器能ping通后端容器,但登录时无法建立连接。
Docker容器日志
2024-01-08 08:41:03 backend | 2024/01/08 06:41:03 INFO loaded config file=config/config.dev.json 2024-01-08 08:41:03 backend | Server running on http://127.0.0.1:8080 2024-01-08 08:41:04 frontend | ▲ Next.js 13.5.4 2024-01-08 08:41:04 frontend | - Local: http://localhost:3000 2024-01-08 08:41:04 frontend | - Network: http://0.0.0.0:3000 2024-01-08 08:41:04 frontend | 2024-01-08 08:41:04 frontend | ✓ Ready in 63ms 2024-01-08 08:42:15 frontend | TypeError: fetch failed 2024-01-08 08:42:15 frontend | at Object.fetch (node:internal/deps/undici/undici:11730:11) 2024-01-08 08:42:15 frontend | at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { 2024-01-08 08:42:15 frontend | cause: Error: connect ECONNREFUSED 127.0.0.1:8080 2024-01-08 08:42:15 frontend | at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16) 2024-01-08 08:42:15 frontend | at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:128:17) { 2024-01-08 08:42:15 frontend | errno: -111, 2024-01-08 08:42:15 frontend | code: 'ECONNREFUSED', 2024-01-08 08:42:15 frontend | syscall: 'connect', 2024-01-08 08:42:15 frontend | address: '127.0.0.1', 2024-01-08 08:42:15 frontend | port: 8080 2024-01-08 08:42:15 frontend | } 2024-01-08 08:42:15 frontend | }
docker compose.yaml
version: '3.9' services: frontend: container_name: frontend image: frontend build: context: ./frontend dockerfile: frontend.dockerfile ports: - '3000:3000' restart: always depends_on: - backend networks: - social-network backend: container_name: backend image: backend build: context: . dockerfile: backend/backend.dockerfile ports: - "8080:8080" networks: - social-network networks: social-network:
frontend.dockerfile
FROM node:18-alpine AS base FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ RUN \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \ else echo "Lockfile not found." && exit 1; \ fi FROM base AS builder WORKDIR /app ENV BUILD_STANDALONE true COPY --from=deps /app/node_modules ./node_modules COPY . . RUN yarn build FROM base AS runner WORKDIR /app ENV NODE_ENV production RUN apk add --no-cache curl RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public RUN mkdir .next RUN chown nextjs:nodejs .next COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/.next ./_next COPY --from=builder --chown=nextjs:nodejs /app/public ./public USER nextjs EXPOSE 3000 ENV PORT 3000 ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"]
backend.dockerfile
FROM golang:latest WORKDIR /app COPY backend/go.mod backend/go.sum ./ RUN go mod download RUN apt-get update && apt-get install -y curl RUN apt-get clean && rm -rf /var/lib/apt/lists/* COPY backend/ . RUN go build -o main ./server.go EXPOSE 8080 CMD ["./main"]
Login.tsx
"use client" import { useRouter } from "next/navigation"; import { FormEvent, useState } from "react"; import { isPasswordValid, isUsernameOrEmailValid } from "./validation"; export default function Login() { const [userId, setUserId] = useState(''); const [password, setPassword] = useState(''); const [usernameOrEmailError, setUsernameOrEmailError] = useState(''); const [passwordError, setPasswordError] = useState(''); const router = useRouter() const [isLoading, setIsLoading] = useState(false); const [canSubmit, setCanSubmit] = useState(true); async function onSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault() setIsLoading(true) let canSubmitForm = true; try { setIsLoading(true); const userIdValid = isUsernameOrEmailValid(userId); if (!userIdValid) { setUsernameOrEmailError("Invalid username"); setCanSubmit(false); canSubmitForm = false; return; } setUsernameOrEmailError(""); const passwordValid = isPasswordValid(password); if (!passwordValid) { setPasswordError("Invalid password"); setCanSubmit(false); canSubmitForm = false; return; } setPasswordError(""); if (canSubmit || canSubmitForm) { const formData = { userId, password } setUserId(''); setPassword(''); const response = await fetch("http://127.0.0.1:8080/api/v1/users/login", { method: 'POST', body: JSON.stringify(formData), mode: "cors", headers: { 'Content-Type': 'application/json' }, credentials: 'include', }) if (!response.ok) { return response.text().then(errorMessage => { console.error(`Error: ${response.status}`); console.error(errorMessage); }); } } else { return; } } catch (error) { console.error('Error during login:', error); } finally { setIsLoading(false); if (canSubmitForm) { router.refresh(); } } } return (<> .... .... .... </>) }
解决方案
1. 修正Go后端监听地址
日志显示后端服务绑定在127.0.0.1:8080,这意味着服务仅能在容器内部的回环地址访问,前端容器无法通过Docker网络连接到它。需要修改Go服务器代码,将监听地址改为0.0.0.0:8080,这样服务会绑定到容器的所有网络接口,允许Docker网络中的其他容器访问。
示例修改(server.go):
// 原代码 log.Fatal(http.ListenAndServe("127.0.0.1:8080", router)) // 修改为 log.Fatal(http.ListenAndServe("0.0.0.0:8080", router))
2. 修正前端API请求地址
前端代码中直接写死了http://127.0.0.1:8080,在Docker环境下,前端容器的127.0.0.1指向自身,而非后端容器。需要将请求地址改为Docker Compose服务名backend(对应compose文件中的服务名称),Docker会自动通过内部网络解析到后端容器。
修改Login.tsx中的fetch地址:
// 原代码 const response = await fetch("http://127.0.0.1:8080/api/v1/users/login", { ... }) // 修改为 const response = await fetch("http://backend:8080/api/v1/users/login", { ... })
额外优化:使用环境变量管理API地址
为了适配不同环境(本地/Docker),建议将API地址通过环境变量配置:
- 在前端项目的
.env文件中添加:
# 本地环境 NEXT_PUBLIC_API_URL=http://127.0.0.1:8080
- 在
docker-compose.yaml的frontend服务中添加环境变量:
services: frontend: # ... 其他配置 environment: - NEXT_PUBLIC_API_URL=http://backend:8080
- 修改Login.tsx中的fetch代码:
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/users/login`, { ... })
内容的提问来源于stack exchange,提问作者Egon
相关产品推荐
相关产品推荐

