Skip to content

Auth Emulator fails with initializeServerApp when running fully inside Docker #9317

@lucasoares

Description

@lucasoares

Operating System

Windows 11 (Docker host) running Running via Docker Compose (bridge network) and with alpine containers

Environment (if applicable)

Next.js 15.5.4 Node.js v22

Firebase SDK Version

12.4.0

Firebase SDK Product(s)

Auth

Project Tooling

Next.js with TypeScript
Docker & Docker Compose
Firebase Emulator running on Docker
Server-side rendering using initializeServerApp

Detailed Problem Description

I have a Next.js system setup with Firebase, and I want to run it locally using the Firebase Emulator, but fully inside Docker.

The Next.js app contains both:

  • Frontend (executed on client machines)
  • Backend (APIs and server components executed on the server)

Running locally works fine if I use localhost for all emulator hosts.

However, when everything runs inside Docker Compose (Next.js app + emulator), it fails on the server side when using initializeServerApp with a auth id token. In this case I need to use localhost in the client-side configurations and the container name in the server-side configurations.

Maybe when using emulator both client and server emulator host needs to be the same?? I could not find anything like that in the documentation.

Works When

Running Next.js locally and only the emulator in Docker:

  • Both server and client connect to the emulator using localhost
  • Everything works (user sign-up, login, authenticated server requests, etc.)

Fails When

Running both Next.js and the emulator in Docker Compose:

  • Client can create an account and log in successfully
  • Server receives the request and tries to initialize the server app
  • Fails with the following error:
FirebaseServerApp could not login user with provided authIdToken: Error [FirebaseError]: Firebase: Error (auth/network-request-failed). at p (.next/server/chunks/2514.js:1:23892) at l (.next/server/chunks/2514.js:1:23452) at A (.next/server/chunks/2514.js:1:29337) at async ai.initializeCurrentUserFromIdToken (.next/server/chunks/2514.js:1:50019) at async (.next/server/chunks/2514.js:1:49521) { code: 'auth/network-request-failed', customData: [Object] } 

The only difference is the emulator host configuration:

  • Client-side must use localhost
  • Server-side must use the Docker container hostname (e.g. firebase-emulator:9099)

Steps and code to reproduce issue

Steps and Code to Reproduce Issue

1. Client Firebase Configuration

'use client'; import { initializeApp } from 'firebase/app'; import { getAuth } from 'firebase/auth'; import { getFirestore } from 'firebase/firestore'; import { getStorage } from 'firebase/storage'; import { firebaseConfig } from '@/services/firebase/config'; import { enableAuthEmulatorIfEnabled, enableFirestoreEmulatorIfEnabled, enableStorageEmulatorIfEnabled } from './emulator'; export const firebaseApp = firebaseConfig ? initializeApp(firebaseConfig) : initializeApp(); export const auth = getAuth(firebaseApp); enableAuthEmulatorIfEnabled(auth); export const db = getFirestore(firebaseApp); enableFirestoreEmulatorIfEnabled(db); export const storage = getStorage(firebaseApp); enableStorageEmulatorIfEnabled(storage);

firebaseConfig is only undefined if I run this application on Firebase App Hosting, in the test neither of the cases will have firebaseConfig undefined (running locally or running on docker). I do not use App Hosting emulator for now.


2. Firebase Admin Configuration

import 'server-only'; import admin from 'firebase-admin'; function getFirebaseAdminConfig(): admin.AppOptions | undefined { if ( typeof process === 'undefined' || process.env.FIREBASE_WEBAPP_CONFIG ) { return undefined; } let options: admin.AppOptions = { storageBucket: process.env.FIREBASE_ADMIN_STORAGE_BUCKET, projectId: process.env.FIREBASE_ADMIN_PROJECT_ID, } if (process.env.FIREBASE_ADMIN_PRIVATE_KEY) { options.credential = admin.credential.cert({ privateKey: process.env.FIREBASE_ADMIN_PRIVATE_KEY, clientEmail: process.env.FIREBASE_ADMIN_CLIENT_EMAIL, projectId: process.env.FIREBASE_ADMIN_PROJECT_ID, }); } return options; } const firebaseAdminConfig = getFirebaseAdminConfig(); let adminApp: admin.app.App; try { adminApp = admin.app(); } catch (_error) { adminApp = firebaseAdminConfig ? admin.initializeApp(firebaseAdminConfig) : admin.initializeApp(); } export const adminAuth = admin.auth(adminApp); export const adminStorage = admin.storage(adminApp); export const adminFirestore = admin.firestore(adminApp);

Connects automatically with the emulator when using specific environment variables, but in this example the admin client is not used because user can't even login to perform actions in the application that requires the admin client.


3. Server Firebase Initialization

import 'server-only'; import { FirebaseServerApp, FirebaseServerAppSettings, initializeServerApp } from 'firebase/app'; import { cookies } from 'next/headers'; import { firebaseConfig } from '@/services/firebase/config'; import { logger } from '../../lib/logger/default-logger'; import { enableAuthEmulatorIfEnabled } from './emulator'; export async function getAuthenticatedAppForUser() { const authIdToken = (await cookies()).get('__session')?.value; var firebaseServerApp: FirebaseServerApp; try { var settings: FirebaseServerAppSettings = authIdToken ? { authIdToken } : {}; firebaseServerApp = firebaseConfig == undefined ? initializeServerApp(settings) : initializeServerApp(firebaseConfig, settings); } catch (error) { logger.warn('Firebase server app initialization failed:', error); throw error; } const auth = getAuth(firebaseServerApp); enableAuthEmulatorIfEnabled(auth); await auth.authStateReady(); return { auth, firebaseServerApp, currentUser: auth.currentUser }; }

Because of #8347 (comment) I tried to replace the enableAuthEmulatorIfEnabled call with a direct call to the connectAuthEmulator with hardcoded host to check if maybe my enableAuthEmulatorIfEnabled function was taking more time than necessary to run, but since the authentication is a tick after initializing auth, I don't think this can be an issue. The enableAuthEmulatorIfEnabled works fine when using localhost for both server and client hosts.


4. Emulator Enabler Function

export function enableAuthEmulatorIfEnabled(auth: Auth) { if (!process.env.NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_HOST && !process.env.FIREBASE_AUTH_EMULATOR_HOST) { return; } if (process.env.NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_HOST === 'disabled' || process.env.FIREBASE_AUTH_EMULATOR_HOST === 'disabled') { return; } const emulatorHost = process.env.FIREBASE_AUTH_EMULATOR_HOST || process.env.NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_HOST; if (!emulatorHost) return; if (!auth.emulatorConfig) { const [host, port] = emulatorHost.split(':'); connectAuthEmulator(auth, `http://${host}:${port}`, { disableWarnings: true }); } }

Initially I didn’t have the auth.emulatorConfig check, but added it because of firebase-admin-node#2647.


5. Docker Compose Setup

firebase-emulator: image: spine3/firebase-emulator:latest working_dir: /firebase environment: - ENABLE_UI=true - GCP_PROJECT=demo-build-manage-scale ports: - 9000:9000 - 8080:8080 - 4000:4000 - 9099:9099 - 8085:8085 - 5001:5001 - 9199:9199 volumes: - firebase_data:/firebase/baseline-data - ../../firebase.json:/firebase/firebase.json - ../gcp/storage/storage.rules:/firebase/infra/gcp/storage/storage.rules nextjs: build: context: ../../ dockerfile: infra/docker/nextjs/Dockerfile args: DOT_ENV_FILE: .env.docker restart: always ports: - "3000:3000" depends_on: firebase-emulator: condition: service_healthy env_file: - required: false path: ../../.env.docker networks: - my-network networks: my-network: driver: bridge volumes: firebase_data:

6. Environment Variables

Local (works):

NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 NEXT_PUBLIC_FIREBASE_STORAGE_EMULATOR_HOST=localhost:9199 NEXT_PUBLIC_FIRESTORE_EMULATOR_HOST=localhost:8080 NEXT_PUBLIC_FIREBASE_PROJECT_ID=demo-build-manage-scale NEXT_PUBLIC_FIREBASE_API_KEY=demo-build-manage-scale FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 FIREBASE_STORAGE_EMULATOR_HOST=localhost:9199 FIRESTORE_EMULATOR_HOST=localhost:8080 FIREBASE_ADMIN_PROJECT_ID=demo-build-manage-scale GCLOUD_PROJECT=demo-build-manage-scale 

Docker Compose (fails) even tho server can perform requests on the emulator without server app:

NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 NEXT_PUBLIC_FIREBASE_STORAGE_EMULATOR_HOST=localhost:9199 NEXT_PUBLIC_FIRESTORE_EMULATOR_HOST=localhost:8080 NEXT_PUBLIC_FIREBASE_PROJECT_ID=demo-build-manage-scale NEXT_PUBLIC_FIREBASE_API_KEY=demo-build-manage-scale FIREBASE_AUTH_EMULATOR_HOST=firebase-emulator:9099 FIREBASE_STORAGE_EMULATOR_HOST=firebase-emulator:9199 FIRESTORE_EMULATOR_HOST=firebase-emulator:8080 FIREBASE_ADMIN_PROJECT_ID=demo-build-manage-scale GCLOUD_PROJECT=demo-build-manage-scale 

Additional Notes

  • When running locally, everything works correctly (both client and server connect to the emulator using localhost).
  • When both run in Docker Compose, the client can authenticate, but the server fails on initializeServerApp with auth/network-request-failed.

Possibly Related Issues

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions