Docker
Firenook runs in a Linux container as it runs on your machine. Four things matter: an image with Node 22, 24 or 26 and glibc 2.28 or later, the emulators listening on all interfaces, the ports published, and a volume for the data you want to keep.
Pick the image
Section titled “Pick the image”Use node:24-slim. Firenook’s Linux engine needs glibc 2.28 or later, and
the official Node images built on Debian have it:
node:24-slim(Debian 12, glibc 2.36) works, and is the image Firenook’s own release checks start the engine in.node:24is the same system with more tools.node:24-trixie-slim(Debian 13, glibc 2.41) works too.node:24-alpineuses musl, not glibc, and Firenook has no engine for it.
For Node 22 or 26, use the same variant of node:22 or node:26.
CI and GitHub Actions shows what
Firenook prints on a musl image.
A Dockerfile
Section titled “A Dockerfile”This one is for a project firenook init set up: Firenook in the root
package.json and a TypeScript Functions codebase in functions/, each with
its lockfile.
# A glibc image with Node: Debian, not Alpine.FROM node:24-slim
WORKDIR /app
# Firenook and its engine for this platform (linux-x64 or linux-arm64).COPY package.json package-lock.json ./RUN npm ci
# Your Functions' own dependencies.COPY functions/package.json functions/package-lock.json functions/RUN npm ci --prefix functions
COPY . .
# Compile the TypeScript codebase into functions/lib.RUN npm run build --prefix functions
# Every emulator on all interfaces, data kept in .firenook/state.CMD ["npx", "firenook", "emulators:start", "--host", "0.0.0.0", "--keep-data"]A JavaScript codebase has no build script: leave out the RUN npm run build
line.
node_modulesfunctions/node_modulesfunctions/lib.firenookThe lockfile lists the engine of every platform, so npm ci installs the
Linux engine in the image even when you wrote the lockfile on a Mac. Keep
node_modules out of the build context: the engine installed on your machine
is not the one the container needs.
Listen on all interfaces
Section titled “Listen on all interfaces”By default the emulators listen on the loopback address, 127.0.0.1 (the hub
and the console on ::1 as well), which inside a container only the
container itself can reach. --host 0.0.0.0 makes every emulator,
the hub and the console listen on all interfaces. Firenook warns you as it
starts, because the emulators have no authentication:
Firenook: binding 0.0.0.0; the emulators have no authentication, so every service, the data and arbitrary Functions execution are reachable from any device that can reach this host.Setting the same host on every entry under emulators in firebase.json
does the same, as with the official emulator. Firenook listens on one address
for the whole suite, so the host values must match.
The hub and the variables Firenook sets still give clients 127.0.0.1 as the
address, the one that reaches a port published to your machine. Bound to
0.0.0.0, the console answers whatever host name you reach it by.
--allowed-hosts NAME,... limits it to those names and the loopback ones.
Publish the ports and keep the data
Section titled “Publish the ports and keep the data”Publish each port under the same number, since clients use the ports that
firebase.json names. Mount a volume on /app/.firenook to keep the data
--keep-data saves in .firenook/state:
services: firebase: build: . ports: - "4000:4000" # console - "4400:4400" # hub - "4500:4500" # logging - "5001:5001" # Functions - "8080:8080" # Firestore - "8085:8085" # Pub/Sub - "9099:9099" # Auth - "9150:9150" # Firestore WebSocket - "9199:9199" # Storage - "9299:9299" # Eventarc - "9499:9499" # Cloud Tasks volumes: - firenook-data:/app/.firenook stop_grace_period: 1m
volumes: firenook-data:docker compose up --buildYour app on your machine connects as usual, for example with
connectFirestoreEmulator(db, '127.0.0.1', 8080), and the console is at
http://127.0.0.1:4000/console. Another container in the same Compose project
reaches the emulators by the service name, for example
FIRESTORE_EMULATOR_HOST=firebase:8080.
The volume holds each start’s working files in .firenook/runs/ too.
Firenook never deletes them; remove old runs when you no longer need them.
Stop it cleanly
Section titled “Stop it cleanly”docker compose stop and docker stop send SIGTERM. Firenook passes it to
the engine, which closes the data and stops cleanly; with --export-on-exit,
it exports first. Docker waits 10 seconds by default before it kills the
container. A large data set can need longer to export, so the Compose file
above allows a minute (docker stop -t 60 does the same). After a kill, the
next start checks the data and repairs it if it needs to.
Run your tests in the image
Section titled “Run your tests in the image”emulators:exec works in the container as it does anywhere:
docker run --rm $(docker build -q .) npx firenook emulators:exec "npm test"Run only the emulators you need with one service on its own.