Google Cloud Services - Firebase Dev Services
This extension implements DevServices for applications developed on with the Google Firebase platform. The DevService runs the appropriate emulators of the Firebase platform based on your configuration.
Be sure to have read the Google Cloud Services extension pack global documentation before this one, it contains general configuration and information.
Current status
The following emulators have been verified to work:
-
Firebase Auth
-
Firebase Firestore
-
Firebase Emulator UI
-
Realtime Database
-
PubSub
-
Cloud Storage
-
Firebase Hosting
-
Functions
The following emulators are currently not supported:
-
EventArc
Currently you can specify a custom firebase.json file but suport for this is limited. A
future version will support reading the configuration from the firebase.json file
instead of from the Quarkus configuration.
Bootstrapping the project
First, we need a new project. Create a new project with the following command (replace the version placeholder with the correct one):
mvn io.quarkus:quarkus-maven-plugin:${quarkusVersion}:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=firebase-admin-quickstart \
-Dextensions="quarkus-google-cloud-firebase-devservices"
cd firebase-admin-quickstart
This command generates a Maven project, importing the Google Cloud Firebase extension.
If you already have your Quarkus project configured, you can add the quarkus-google-cloud-firebase extension to your project by running the following command in your project base directory:
./mvnw quarkus:add-extension -Dextensions="quarkus-google-cloud-firebase"
This will add the following to your pom.xml:
<dependency>
<groupId>io.quarkiverse.googlecloudservices</groupId>
<artifactId>quarkus-google-cloud-firebase</artifactId>
</dependency>
If you already have a firebase project (which you can create using the firebase tools by running firebase init), you need to make the following changes to get going:
* Add "host" : "0.0.0.0" to all emulator entries
* Add the hub, logging and UI emulators to the emulators configuration
{
"emulators" : {
"ui": {
"port": 4000,
"enabled": true,
"host": "0.0.0.0"
},
"hub": {
"port": 4400,
"host": "0.0.0.0"
},
"logging": {
"port": 4500,
"host": "0.0.0.0"
}
}
}
-
If you use Firestore, also set the Firestore Websocket port
{
"emulators" : {
"firestore": {
"port": 8080,
"websocketPort" : 9150,
"host": "0.0.0.0"
}
}
}
-
For both entries, you can of course use your own custom ports, where needed.
The extension will try to read the firebase.json file from the current working directory the
process was started in (it will not attempt to traverse the directory upwards to try to find the
file). In some cases you may need to specify this working directory. E.g. when using Gradle:
quarkusDev {
workingDirectory = rootProject.projectDir
}
Firebase tools version
You can specify the firebase (tools) version to use, which determines the version of the emulators which are run
in the configuration. However, if a package.json file is present which defines this version (eiter in
dependencies or devDependencies) this version will automatically be picked up in no specific version is
configured. If neither a version is configured, nor a package.json file can be found, the latest version of
firebase-tools is used.
Base image caching
This extension builds a custom Docker image to execute the firebase emulators. To reduce startup times, an aggressive
caching strategy is used. The extension will build an internal base image (called
localhost/testcontainers/firebase-base:{firebase-version}) and will only rebuild this image if it cannot be found.
If you need the image to be regenerated, you manually need to remove the docker image.
Currently the base image builder uses the following steps: - Select the base image (based on the image name, see below) - Clean up the image and install needed dependencies - Create a user and group if needed - Assign ownership to the selected user/group - Switch to the specified user - Download and cache all Firebase emulators
This means that if you need to alter any of the steps previously mentioned, you need to remove the mentioned base image.
Custom Docker image
To run the emulators, a custom Docker image is build on the fly to run the Firebase emulators. This image is based on a NodeJS based image (refer to the configuration of the default value of quarkus.google.cloud.firebase.devservice.image-name to see the base image).
You can configure a custom image if needed as base image to run the Firebase Emulators in. This image has the following requirements:
-
The image must support NodeJS in a version compatible with the required Firebase Tools
-
The image must be
alpinebased (or at least able to install the following packages usingapk: )-
openjdk21-jre
-
bash
-
curl
-
openssl
-
gettext
-
nano
-
nginx
-
sudo
-
Custom Firebase JSON
If emulators are configured via the configuration options, a firebase.json file is generated inside the image to configure the various emulators. You can configure the Dev Services to use your own custom firebase.json file (e.g generated using the Firebase tools CLI). The following requirements are defined for this file:
-
Each of the emulators must be exposed on
0.0.0.0as host as described here. If this is not done, the Emulators will not be reachable from the Docker host. -
Emulators need to be configured to use the default ports. Customizing the ports on which they run is currently not supported (this might change in a future version).
Connectivity
Each emulator’s address is exposed to your application through two config properties, because which one is
actually reachable depends on where your code runs. These properties are consumed by the respective extensions
themselves (FirestoreProducer, FirebaseAdminProducer, StorageProducer, FirebaseDatabaseProducer, …) - you
normally never read them directly, but it helps to know which is which when debugging connectivity issues.
Emulator |
App-facing property |
Companion-container property |
Default |
|---|---|---|---|
Auth |
|
|
random port |
Emulator Suite UI |
|
|
random port |
Firebase Hosting |
|
|
random port |
Cloud Functions |
|
|
random port |
EventArc |
|
|
random port (only settable via a custom |
Realtime Database |
|
|
random port |
Cloud Firestore |
|
|
random port |
Cloud Storage |
|
|
random port |
Pub/Sub |
|
|
random port |
Every emulator listed above defaults to a randomly Docker-assigned host port; you can pin one to a specific port via
its own emulator-port config property (e.g. quarkus.google.cloud.devservices.firebase.firestore.emulator-port).
The Vite dev server (see below) is the one exception: it is always published on host port 5173, with no
configuration option.
Throughout this page, the Docker host refers to getHost():getMappedPort() (Testcontainers) - the address
your emulator container’s published ports are reachable on from outside Docker. This is usually, but not always,
literally localhost: getHost() resolves via DockerClientProviderStrategy.resolveDockerHostIpAddress(), which
returns localhost for a local Docker Desktop/Engine reached over a unix socket, the daemon’s own host for a
remote/TCP Docker daemon, and the Docker bridge network’s gateway IP when the build itself runs inside a container
(a common "Docker-outside-of-Docker" CI setup) - TESTCONTAINERS_HOST_OVERRIDE overrides all of this when set.
|
The app-facing property (first column) resolves to the Docker host when shared-network mode is inactive, and to the
shared-network alias whenever it is active - mirroring the same blanket useSharedNetwork check the sibling
Google Cloud Dev Services (Firestore, PubSub, …) already use. In practice, shared-network mode is normally only
active because Quarkus itself requires it for a containerized @QuarkusIntegrationTest run, which it attaches to
that same network automatically, so the alias resolves correctly.
If you manually force shared-network mode on for an unrelated reason
(quarkus.devservices.launch-on-shared-network=true) while your actual consumer is still a plain host-JVM process
(quarkus:dev, a regular @QuarkusTest, a non-containerized @QuarkusIntegrationTest), that consumer gets the
alias here too and won’t be able to reach it.
|
The companion-container property (second column) is populated by default, regardless of network mode. It exists for
a container your own test code starts via Testcontainers in the same JVM (e.g. a Playwright browser container)
that needs to reach the emulator - reachable via the host.testcontainers.internal ambassador hostname that
Testcontainers.exposeHostPorts() sets up. Since this starts one extra ambassador container (shared across the
whole JVM - harmless if something else already triggered it, but wasted if nothing ever needs it), you can turn it
off entirely if your tests never start a companion container that needs to reach the emulator:
quarkus.google.cloud.devservices.firebase.emulator.expose-to-companion-containers=false
Situation |
Address |
|---|---|
Host-JVM consumer ( |
The Docker host |
Host-JVM consumer, shared-network manually forced active for an unrelated reason |
Shared-network alias - unreachable from a host-JVM process, see the caution above |
|
|
A companion container your test starts itself, also attached to the shared network |
|
A companion container your test starts itself, not attached to the shared network |
|
Browser, from the host machine |
The Docker host |
The two shared-facing forms use different port numbers, and mixing them up will simply fail to
connect. The network alias always addresses the emulator’s internal container port; the
host.testcontainers.internal ambassador always forwards to the host-mapped port published on the Docker host -
never the internal one, since the ambassador itself runs outside the container and forwards through the Docker
host’s own published port mapping. This distinction disappears (both numbers are equal) once an emulator is pinned
to a fixed port, or for the Vite dev server.
|
Details on specific Devservices
The following sections provide documentation in interaction with specific emulators.
Hosting emulator
If you use the hosting emulator, where Quarkus is the backend, you will need to include a CORS configuration, as REST requests will originate from another Origin (host). See the Quarkus CORS documentation for more info.
A simple setup would be
"%dev".quarkus.http.cors=true
"%dev".quarkus.http.cors.origins: /.*/
Note that a redirect from the hosting emulator to the Quarkus instance is currently not supported by the emulator.
Web framework dev servers
When the Hosting emulator is enabled, the DevService inspects the configured hosting directory to detect whether it
contains a supported frontend framework project, using the same detection signals firebase-tools itself uses. If a
supported framework is detected, the generated Docker image is adjusted so the framework’s own dev server (including
its hot-reload/HMR client) can be used from inside the container. What exactly is adjusted, and what you may need to
configure in your own project, is detailed per framework below.
Currently supported frameworks:
-
Vite
Vite
firebase-tools spawns the project’s own vite binary directly, without any CLI flags, and serves pages through
the Hosting emulator using a plain HTTP request/response proxy that does not forward WebSocket upgrades. To make
hot-reload work from inside this DevService, the following adjustments are applied to the Docker image:
-
File-change events on the bind-mounted host directory are not reliably delivered as
inotifyevents into the container, soCHOKIDAR_USEPOLLINGis enabled to fall back to filesystem polling, whichchokidar(Vite’s file watcher) honors. -
Vite’s dev server port (
5173) is published on the Docker host under the same port number, since Vite’s HMR client reconnects directly to that port rather than through the Hosting emulator’s own proxy. -
The resolved port is also set as the
VITE_HMR_PORTenvironment variable inside the container, sovite.config.js/vite.config.tscan read it back instead of hardcoding it.
On top of that, two things need to be configured explicitly in your own vite.config.js/vite.config.ts for
hot-reload to actually work:
export default defineConfig({
server: {
// Vite defaults to binding only the loopback interface, which is unreachable from the Docker
// host even though the dev server port is published.
host: true,
hmr: {
// Read back the port the DevService actually published (see VITE_HMR_PORT below), falling back
// to Vite's own default outside the emulator (e.g. a plain `vite dev` on your machine). Without
// this, the HMR client falls back to the page's own port (the Hosting emulator's port), which
// cannot proxy WebSocket connections and will never connect.
clientPort: Number(process.env.VITE_HMR_PORT) || 5173,
},
},
})
process.env works here regardless of the VITE_ prefix - that prefix only governs which variables Vite exposes
to client-side code via import.meta.env; vite.config.js itself is a plain Node.js script and can read any
environment variable. Reading it back this way, instead of hardcoding a number, keeps vite.config.js and the
DevService in sync automatically, including if you configure a different port per profile (see below).
Without these settings, the page loads and the browser console shows the dev server connecting (e.g. "[vite] connecting…"), but file changes never trigger a reload, since the HMR WebSocket never actually reaches the Vite dev server.
Unlike every other emulator managed by this DevService, the Vite HMR port is published on a fixed Docker host
port rather than a randomly assigned one, since the browser’s HMR client needs to reach it directly at a
predictable address. If your own vite.config.js/vite.config.ts overrides server.port to something other than
Vite’s default of 5173, tell the DevService about it so it publishes the right port:
quarkus.google.cloud.devservices.firebase.hosting.vite.hmr-port=5174
It’s also recommended to set server.strictPort: true in vite.config, so Vite fails fast instead of silently
falling back to a different port if the configured one is unavailable inside the container. The DevService logs a
best-effort warning at startup if it detects a likely mismatch or a missing strictPort setting, but this is a
plain-text heuristic (not a full JS/TS parser), so it can’t reliably catch every case - except when it detects
vite.config reading server.port from process.env.VITE_HMR_PORT, which it recognizes as a confirmed match
rather than an unknown case.
Because this port is fixed rather than random, running two instances of the emulator at once (e.g. quarkus:dev
together with continuous testing, or quarkus:dev alongside a separately started mvn test) can fail with a
"port is already allocated" error if both try to publish the same Vite HMR port. If you need dev mode and tests to
run side by side, configure a different hmr-port for the %test profile. If your vite.config.js/vite.config.ts
reads server.port from VITE_HMR_PORT as shown above, that’s all you need to do - the config stays in sync
automatically. If you hardcode the port instead, you’ll also need to mirror that same value there by hand.
Auth emulator
You can use the features provided by Mircoprofile JWT (e.g. injecting a @Claim value) by including the smallrye-jwt
extension and disabling smallrye-jwt using the following property. This will prevent SmallRye JWT from handling the JWT
validation (leaving that to the Firebase Auth module and the Auth emulator), but using the provided JWT to allow injecting
of these beans.
quarkus.smallrye-jwt.enabled=false
Interaction with other extensions
The following extensions support Dev Services which conflicts with the Dev Services exposed by the Firebase Emulators.
-
Firestore
-
PubSub
When including this module, these Dev Services will automatically be disabled, as the Firebase emulator should feature wise be on-par or more extensive than the individual emulators.
Specific environments
For some environments, there are additional steps to take to run this extension:
Environment |
Type |
|---|---|
Bitbucket |
Use cloud runtime v3 to make sure the Firebase Emulator can mount volumes (needed for e.g Hosting)
|
Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
|---|---|---|
Indicates to use the dev service for Firebase. The default value is true. This indicator is used to detect the Firebase DevService and disable the DevServices for extensions which conflict with the Firebase DevService. Environment variable: |
boolean |
|
The version of the firebase tools to use. Default is to auto-detect it from a package.json file or else use the latest available version. Environment variable: |
string |
|
Sets the Docker image name for the Google Cloud SDK. This image is used to emulate the Pub/Sub service in the development environment. The default value is 'node:23-alpine'. See also the documentation on Custom Docker images for more info about this image. Environment variable: |
string |
|
Id of the docker user to run the firebase executable. This is needed in environments where Docker does not perform a mapping to the user running Docker. In a Docker Desktop setup, Docker automatically performs this mapping and the data written by the emulator can be read by the user running the build. This is not the case in a regular (non-Desktop) setup, so you may need to set the user id and Environment variable: |
int |
|
Id of the group to which the Environment variable: |
int |
|
Try to read the Environment variable: |
string |
|
Try to read the Environment variable: |
string |
|
Automatically try to detect the Environment variable: |
boolean |
|
Pipe Stdout of the container to the Quarkus logging Environment variable: |
boolean |
|
Pipe Stedd of the container to the Quarkus logging Environment variable: |
boolean |
|
List of additional environment variables and values which can be passed into the docker image. These variables can be picked up by some webframeworks or functions (see the firebase documentation). This feature can e.g. be used to pass in for example the port the Quarkus application is exposing. Environment variable: |
Map<String,String> |
|
The token to use for firebase authentication. Run Environment variable: |
string |
|
Sets the JAVA tool options for emulators based on the Java runtime environment like -Xmx. See also here Environment variable: |
string |
|
Allow to import and export data. Specify a path relative to the current working directory of the executable (for most unit tests, this is the root of the build directory) to be used for import and export of emulator data. The data will be written to a subdirectory called "emulator-data" of this directory. See also here Environment variable: |
string |
|
Indicate whether to import, export or both the data specified in Environment variable: |
|
|
Indicates the set of experimental features from firebase to enable (using the firebase experiment:enable command line option). Environment variable: |
list of string |
|
Enable firebase emulators debugging. Environment variable: |
boolean |
|
Indicate to use a custom firebase.json file instead of the automatically generated one. The custom firebase.json file MUST include a setting of
to ensure the ports of the emulator are exposed correctly at the docker container level. See the section on Custom Firebase Json in the docs for more info. Environment variable: |
string |
|
Indicates whether the service should be enabled or not. The default value is 'false'. Environment variable: |
boolean |
|
Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. Environment variable: |
int |
|
Port on which to expose the logging endpoint port. This is needed in case you want to view the logging via the Emulator UI. Environment variable: |
int |
|
Port on which to expose the hub endpoint port. This is needed if you want to use the hub API of the Emulator UI. Environment variable: |
int |
|
Indicates whether the DevService should be enabled or not. The default value is 'false'. Environment variable: |
boolean |
|
Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. Environment variable: |
int |
|
Indicates whether the DevService should be enabled or not. The default value is 'false'. Environment variable: |
boolean |
|
Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. Environment variable: |
int |
|
Path to the hosting files. Environment variable: |
string |
|
Indicates whether the DevService should be enabled or not. The default value is 'false'. Environment variable: |
boolean |
|
Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. Environment variable: |
int |
|
Indicates whether the DevService should be enabled or not. The default value is 'false'. Environment variable: |
boolean |
|
Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. Environment variable: |
int |
|
Port on which to expose the websocket port. This is needed in case the Firestore Emulator UI needs is used. Environment variable: |
int |
|
Path to the firestore.rules file. Environment variable: |
string |
|
Path to the firestore.indexes.json file. Environment variable: |
string |
|
Indicates whether the DevService should be enabled or not. The default value is 'false'. Environment variable: |
boolean |
|
Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. Environment variable: |
int |
|
Indicates whether the DevService should be enabled or not. The default value is 'false'. Environment variable: |
boolean |
|
Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. Environment variable: |
int |
|
Indicates whether the DevService should be enabled or not. The default value is 'false'. Environment variable: |
boolean |
|
Specifies the emulatorPort on which the service should run in the development environment. The default is to expose the service on a random port. Environment variable: |
int |
|
Path to the storage.rules file. Environment variable: |
string |
|
Google Cloud project ID. The project is required to be set if you use the Firebase Auth Dev service. Environment variable: |
string |