Firebase to AWS migration

Written by

·

·

Firebase is designed to get you moving fast. The SDKs are clean, the free tier is generous enough to build something real on, and you do not have to think about infrastructure at all. That is the point. The trade-off is that Firebase makes a lot of decisions for you, and at some point those decisions start costing you either money or flexibility — usually both at the same time.

This guide is for teams who have already decided to move and want a clear path to do it. We will use a sensor data service as a running example throughout, but the migration steps apply broadly to any Firebase backend.


Should You Actually Migrate?

This is worth asking honestly before spending time on it. If you are on Firebase’s free Spark plan and your usage is comfortably within the limits, there is no immediate reason to move. The Spark plan gives you 1GB of Firestore storage, 50,000 document reads per day, and 20,000 writes per day. For a lot of small projects, that is enough.

Where things change is when you cross into Blaze territory. Firebase’s Blaze plan is pay-as-you-go — there is no monthly cap, which means a sudden traffic spike or a misbehaving client can produce a bill you did not expect. More importantly, the per-operation pricing model means write-heavy workloads get expensive faster than storage-heavy ones.

To put it in concrete terms: a sensor that writes a reading every 30 seconds produces about 2,880 writes per day by itself. Ten sensors exhaust the free write quota. A hundred sensors push you firmly into paid territory at $0.18 per 100,000 writes. At that point, migrating to AWS starts making economic sense — not because AWS is always cheaper, but because the cost structure is more predictable and the storage costs are lower at scale.

Plan Firestore Storage Writes per Day Reads per Day Cost
Firebase Spark (free) 1 GB 20,000 50,000 Free
Firebase Blaze $0.18/GB/month $0.18 per 100K $0.06 per 100K Pay-as-you-go
AWS DynamoDB (free tier) 25 GB 25 writes/second sustained 25 reads/second sustained Free (permanent)
AWS DynamoDB (on-demand) $0.25/GB/month $1.25 per million $0.25 per million Pay-as-you-go

One thing that surprises people: the AWS DynamoDB free tier is permanent, not a 12-month trial. 25GB of storage and 25 read/write capacity units stay free indefinitely. For our sensor example running 100 sensors at one reading every 30 seconds, that is about 3.3 writes per second — well within the free DynamoDB capacity. The same workload on Firebase would cost around $26/month on Blaze.


Which AWS Service Should You Use?

Firebase Firestore is a general-purpose document database. When you move to AWS, you have more choices, and the right one depends on how you access your data.

AWS Service Best For Storage Cost
Amazon DynamoDB Current state lookups, latest reading per device, low-latency queries by ID $0.25/GB/month
Amazon Timestream Time-series queries, trends, aggregations over time ranges — purpose-built for sensor data $0.036/GB/month (magnetic)
Amazon S3 + Athena Historical data, analytics, archival — cheapest long-term storage with SQL querying $0.023/GB/month
AWS IoT Core + Timestream If devices use MQTT — IoT Core routes directly to Timestream without custom ingestion code Timestream rates apply

For sensor data specifically, Timestream is worth serious consideration. It is a managed time-series database with automatic tiering — recent data lives in a memory store for fast queries, older data moves to magnetic storage automatically. The storage cost at $0.036/GB is dramatically lower than both Firestore and DynamoDB, which matters when you are storing years of high-frequency readings.

A common pattern is to use both: DynamoDB holds the latest reading per device for real-time dashboard lookups, and Timestream or S3 holds the full history for analytics and reporting. This avoids expensive Firestore collection scans, which is often what drives up costs in the first place.


Step 1: Export Your Data from Firebase

The first step is getting your data out. Firebase supports managed Firestore exports directly to Google Cloud Storage via the gcloud CLI.

gcloud firestore export gs://your-gcs-bucket/migration \ --collection-ids=your_collection

This produces a set of export files in Google Cloud Storage. Once the export completes, transfer them to an S3 bucket. For smaller datasets, the gsutil cross-cloud copy works directly:

gsutil -m cp -r gs://your-gcs-bucket/migration \ s3://your-aws-bucket/firestore-export

For large exports over 50GB, use AWS DataSync with a GCS connector instead. It handles parallel transfer, retries, and progress tracking — manually copying tens of gigabytes over a CLI command is fragile and slow.

The exported format is Firestore’s internal LevelDB format, not JSON. Before you can import it into DynamoDB or Timestream, you need to convert it. The most reliable approach is to write a short export script that reads your collections directly via the Firebase Admin SDK and produces newline-delimited JSON:

const admin = require('firebase-admin'); const fs = require('fs');

admin.initializeApp({ credential: admin.credential.applicationDefault() }); const db = admin.firestore();

async function exportCollection(collectionName) { const snapshot = await db.collection(collectionName).get(); const output = fs.createWriteStream(`${collectionName}.ndjson`);

 snapshot.forEach(doc => { output.write(JSON.stringify({ id: doc.id, ...doc.data() }) + '\n'); });

 output.end(); console.log(`Exported ${snapshot.size} documents`); }

exportCollection('sensor_readings');

Upload the resulting .ndjson file to S3, and it becomes the source for your import.


Step 2: Set Up Your AWS Destination

If you are migrating to DynamoDB, create the table with a partition key that matches how you query your data. For sensor readings, that is typically the device ID.

aws dynamodb create-table \ --table-name sensor-readings \ --attribute-definitions \ AttributeName=device_id,AttributeType=S \ AttributeName=timestamp,AttributeType=N \ --key-schema \ AttributeName=device_id,KeyType=HASH \ AttributeName=timestamp,KeyType=RANGE \ --billing-mode PAY_PER_REQUEST

If you are migrating to Timestream, create a database and table with retention policies. The memory store retention controls how long data stays in the fast tier before moving to magnetic storage.

aws timestream-write create-database \ --database-name freezesense

aws timestream-write create-table \ --database-name freezesense \ --table-name readings \ --retention-properties \ MemoryStoreRetentionPeriodInHours=24,MagneticStoreRetentionPeriodInDays=365

Step 3: Import Your Data

With the exported .ndjson file in S3, write a Lambda function or a local script that reads it line by line and writes to your chosen AWS service. For DynamoDB, use batch writes to stay within rate limits and keep import costs low.

import boto3 import json

dynamodb = boto3.resource('dynamodb') table = dynamodb.Table('sensor-readings')

with open('sensor_readings.ndjson', 'r') as f, table.batch_writer() as batch: for line in f: doc = json.loads(line) batch.put_item(Item={ 'device_id': doc['device_id'], 'timestamp': int(doc['timestamp']), 'temperature': str(doc['temperature']), 'humidity': str(doc['humidity']) })

DynamoDB’s batch_writer automatically groups writes into batches of 25 and retries any unprocessed items. You do not need to implement retry logic yourself.


Step 4: Update Your Application

Remove the Firebase SDK and replace Firestore calls with the AWS SDK. The write path changes from a Firestore document set to a DynamoDB put item — or a Kinesis put record if you are routing writes through a stream.

// Before — Firebase await db.collection('sensor_readings').add({ device_id: deviceId, timestamp: Date.now(), temperature: reading.temperature });

// After — DynamoDB const client = new DynamoDBClient({ region: 'us-east-1' }); await client.send(new PutItemCommand({ TableName: 'sensor-readings', Item: { device_id: { S: deviceId }, timestamp: { N: String(Date.now()) }, temperature: { N: String(reading.temperature) } } }));

Run both write paths simultaneously for at least 48 hours before removing Firebase. Compare document counts between Firestore and DynamoDB daily to confirm parity, and only remove the Firebase write path once you are satisfied the counts match.


Final Thoughts

The migration itself is straightforward once you have the export in hand. The harder decision is which AWS service to land on, and that is worth spending time on before writing any import code — moving data twice is more painful than choosing carefully the first time.

If you are currently on Firebase’s free Spark plan and your workload fits within it, there is no urgency. But if you are approaching the Blaze plan or already on it and watching costs grow, the AWS free tier is meaningfully more generous for write-heavy workloads, and services like Timestream and S3 offer storage costs that Firestore cannot match at scale.

The most important thing is not to rush the cutover. Export, import, run in parallel, verify the data, then cut. Doing it in that order takes longer but it means you always have a working system to fall back to if something does not go as planned.

Contact Me

Building AI systems, AWS architectures, and cloud-native applications.
Open to collaboration, consulting, and conversation.

© Shivansh Jain