How to Fix Next.js Broken Pipe on Google Cloud Run


Troubleshooting Next.js “Broken Pipe” on Google Cloud Run

As a Senior DevOps Engineer, encountering a “Broken Pipe” error with your Next.js application on Google Cloud Run can be frustrating. This guide will walk you through the common causes and solutions, focusing on the unique environment of Cloud Run.


1. The Root Cause: Why This Happens on Google Cloud Run

The “Broken Pipe” error (EPIPE) typically indicates that a process tried to write data to a pipe, but the reading end of the pipe was closed before the write operation could complete. In the context of a Next.js application on Google Cloud Run, this almost invariably points to one core issue: your Next.js application is not correctly listening on the port and host that Cloud Run expects.

Google Cloud Run operates by providing your container with a PORT environment variable (always 8080) and expects your application to bind to 0.0.0.0:$PORT. If your Next.js application, by default, attempts to listen on localhost:3000 (or any other port/host), Cloud Run’s internal proxy will be unable to reach it. Consequently:

  1. Health Checks Fail: Cloud Run’s readiness and liveness probes will time out, as the application isn’t responding on the expected interface.
  2. Container Termination: Cloud Run will eventually decide the container is unhealthy and terminate it.
  3. Broken Pipe: During shutdown or before it’s gracefully handled, any active processes (like Node.js itself trying to log to standard output/error) will attempt to write to pipes that are already closed by the terminating parent process or Cloud Run’s infrastructure, resulting in the “Broken Pipe” error.

Less common, but still possible, causes include:

  • Unhandled Exceptions: Your Next.js app might be crashing immediately after startup due to an unhandled error, leading to a quick shutdown and subsequent broken pipe errors for any pending writes.
  • Resource Exhaustion: Memory or CPU limits being hit, causing the container to be forcibly terminated.

However, for Next.js on Cloud Run, the port/host binding is overwhelmingly the most frequent culprit.


2. Quick Fix (CLI)

The fastest way to address the primary cause is to ensure your npm start command explicitly tells Next.js to use the PORT environment variable provided by Cloud Run.

  1. Update your package.json (locally): Modify your start script to explicitly bind Next.js to the PORT environment variable.

    // package.json
    {
      "name": "my-nextjs-app",
      "version": "0.1.0",
      "private": true,
      "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start -p $PORT", // <--- CRITICAL CHANGE
        "lint": "next lint"
      },
      "dependencies": {
        "next": "latest",
        "react": "latest",
        "react-dom": "latest"
      }
    }
    • next start -p $PORT: This command instructs Next.js to start its production server and listen on the port specified by the PORT environment variable. Cloud Run automatically sets PORT to 8080 inside your container.
  2. Redeploy your service using gcloud:

    Assuming you’ve updated your package.json and rebuilt your Docker image (if using a Dockerfile), redeploy your service.

    gcloud run deploy YOUR_SERVICE_NAME \
      --image gcr.io/YOUR_PROJECT_ID/YOUR_IMAGE_NAME:latest \
      --platform managed \
      --region YOUR_REGION \
      --allow-unauthenticated # Or adjust access as needed

    Replace YOUR_SERVICE_NAME, YOUR_PROJECT_ID, YOUR_IMAGE_NAME, and YOUR_REGION with your actual values.

This immediate change often resolves the “Broken Pipe” issue by correctly exposing your Next.js application to Cloud Run’s internal network.


3. Configuration Check

To ensure long-term stability and prevent recurrence, review these critical configuration files:

3.1. package.json

Verify the start script as mentioned in the Quick Fix section. This is the most crucial part.

// package.json
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start -p $PORT", // Ensure this is correct
  "lint": "next lint"
},

3.2. Dockerfile

Ensure your Dockerfile correctly builds the Next.js application and executes the start script.

# Stage 1: Install dependencies and build the application
FROM node:18-alpine AS builder

WORKDIR /app

COPY package.json yarn.lock ./
# If using npm: COPY package.json package-lock.json ./

RUN npm install --frozen-lockfile # Or yarn install --frozen-lockfile

COPY . .

RUN npm run build # Build the Next.js application

# Stage 2: Run the application
FROM node:18-alpine

WORKDIR /app

# Copy only necessary files from the builder stage
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/public ./public
# If you have custom server.js or other necessary files, copy them as well

# Expose the port Cloud Run expects
EXPOSE 8080

# Command to run the application
# This will execute `npm start` which should then use `next start -p $PORT`
CMD ["npm", "start"]

Key Points in Dockerfile:

  • Multi-stage build: Recommended for smaller image sizes.
  • RUN npm run build: Ensure your application is built before deployment. Next.js production mode requires a build.
  • EXPOSE 8080: While Cloud Run doesn’t strictly require EXPOSE, it’s good practice for clarity and container introspection.
  • CMD ["npm", "start"]: This command executes the start script defined in your package.json.

3.3. Cloud Run Service Configuration (Environment Variables)

While Cloud Run provides the PORT environment variable, it’s worth checking your service configuration for any overrides.

  • Via gcloud:

    gcloud run services describe YOUR_SERVICE_NAME \
      --platform managed \
      --region YOUR_REGION \
      --format='json' | jq '.spec.template.spec.containers[0].env'

    Look for any PORT variable you might have explicitly set that could override the default 8080. If you see {"name": "PORT", "value": "3000"}, remove it, as this would force Next.js to listen on the wrong port.

  • Via Google Cloud Console: Navigate to Cloud Run -> Your Service -> “REVISE & DEPLOY NEW REVISION” -> “Container, Networking, Security” tab -> “Variables & Secrets”. Ensure there’s no PORT variable explicitly set to anything other than 8080 (or preferably, just leave it unset to use Cloud Run’s default).


4. Verification

After applying the fixes, verify your deployment:

  1. Check Cloud Run Logs: Go to your Cloud Run service in the Google Cloud Console, then navigate to the “Logs” tab. Look for messages from your application indicating it’s starting and listening on the correct port:

    info  - Ready on http://0.0.0.0:8080

    You should no longer see “Broken Pipe” errors or container crash messages immediately after startup.

  2. Access the Service URL: Open your Cloud Run service’s URL in a web browser. Your Next.js application should load and function correctly.

  3. Monitor Revisions: Ensure that new revisions deploy successfully and remain in a “Ready” state without frequent restarts. If the issue was critical port binding, new revisions should now be stable.

By systematically checking and correcting the port/host binding for your Next.js application on Cloud Run, you can reliably eliminate the “Broken Pipe” error and ensure a stable deployment.