Skip to main content

FireSync

PyPI version Tests Coverage Python 3.8-3.14 License: MIT Platform Code style: black CodeQL TruffleHog

Infrastructure as Code for Google Cloud Firestore

FireSync is a lightweight Python tool that brings version control and deployment automation to your Firestore database schema. Manage composite indexes, single-field indexes, and TTL policies using a familiar pull-plan-apply workflow inspired by Terraform.

Table of Contents

Features

  • ๐Ÿ”„ Version Control - Track Firestore schema changes in git
  • ๐Ÿ” Plan Before Apply - Preview changes before deploying
  • ๐Ÿ›ก๏ธ Safety First - Idempotent operations, no accidental deletions
  • ๐Ÿ”‘ Flexible Auth - Use any GCP service account key
  • ๐Ÿ Zero Dependencies - Pure Python stdlib, just needs gcloud CLI
  • ๐ŸชŸ Cross-Platform - Works on Linux, macOS, and Windows

Quick Start

Prerequisites

  • Python 3.8+
  • Google Cloud SDK (gcloud CLI)
  • GCP service account with Firestore permissions:
    • datastore.indexes.list
    • datastore.indexes.create
    • datastore.indexes.update
    • firebase.projects.get

Installation

Install from PyPI:

pip install firestore-schema-migration

This installs the firesync command globally.

Alternative: Install from source

git clone https://github.com/PavelRavvich/firesync.git
cd firesync
pip install -e .

All Commands

Get help on any command:

firesync --help
firesync pull --help
firesync plan --help
firesync apply --help
firesync env --help

Available Commands:

  • firesync init - Initialize workspace with config.yaml
  • firesync env add <name> - Add environment to workspace
  • firesync env remove <name> - Remove environment from workspace
  • firesync env list - List all environments
  • firesync env show <name> - Show environment details
  • firesync pull - Export Firestore schema to local files
  • firesync plan - Compare local vs remote schemas
  • firesync apply - Deploy local schema to Firestore

Quick Setup

  1. Initialize workspace:
firesync init
  1. Add your environments:
# Using direct path to key file
firesync env add dev --key-path=./secrets/gcp-key-dev.json --description="Development"

# Using environment variable (auto-detects JSON content or file path)
firesync env add prod --key-env=GCP_PROD_KEY --description="Production"
  1. Pull schemas from all environments:
firesync pull --all

Commands Overview

firesync init

Initialize a new FireSync workspace. Creates config.yaml to manage multiple environments.

# Initialize in current directory
firesync init

# Initialize in custom directory
firesync init --path ./my-project

Options:

  • --path PATH - Target directory for workspace (default: current directory)

Creates:

  • config.yaml - Workspace configuration
  • firestore_schema/ - Default schema directory

firesync env

Manage environments in your workspace.

Authentication Options:

FireSync supports two ways to provide GCP service account credentials:

Option Description Use Case
--key-path Direct path to key file Local development
--key-env Environment variable name CI/CD and shared environments

The --key-env option is smart: it auto-detects whether the environment variable contains:

  • JSON content (the key itself) - creates a temp file for gcloud
  • File path (path to key file) - reads the key from that path

Add environment:

# Option 1: Direct path to key file
firesync env add dev --key-path=./secrets/gcp-key-dev.json

# Option 2: Environment variable (auto-detects JSON content or file path)
firesync env add prod --key-env=GCP_PROD_KEY --description="Production environment"

Example environment variable usage:

# Store JSON content in env var (for CI/CD secrets)
export GCP_KEY='{"type":"service_account","project_id":"my-project",...}'

# Or store path to key file in env var
export GCP_KEY='/path/to/service-account.json'

# Both work with the same command:
firesync env add prod --key-env=GCP_KEY

List environments:

firesync env list
# Shows environment names with key paths (including absolute paths) or key env variables
# Example output:
#   dev
#     key_path: secrets/gcp-key-dev.json - Development environment (/absolute/path/to/secrets/gcp-key-dev.json)
#   prod
#     key_env: GCP_PROD_KEY - Production environment

Show environment details:

firesync env show dev

Remove environment:

firesync env remove dev

firesync pull

Export Firestore schema from GCP to local JSON files.

Pull all environments:

firesync pull --all

Pull single environment:

firesync pull --env=dev

Creates three JSON files in schema directory:

  • composite-indexes.json - Composite indexes
  • field-indexes.json - Single-field indexes
  • ttl-policies.json - TTL policies

firesync plan

Compare local schema against remote Firestore and preview changes.

Compare local vs remote:

firesync plan --env=dev

Migration mode - compare two environments (local vs local):

firesync plan --env-from=dev --env-to=staging

With custom schema directory:

firesync plan --env=dev --schema-dir=custom_schemas

Output shows:

  • [+] WILL CREATE - Resource exists locally but not remotely
  • [-] WILL DELETE - Resource exists remotely but not locally
  • [~] WILL UPDATE - Resource differs between local and remote
  • [~] No changes - Schemas are in sync

firesync apply

Deploy local schema to Firestore.

Apply to environment:

firesync apply --env=dev

Migration mode - apply source schema to target environment:

firesync apply --env-from=dev --env-to=staging

With custom schema directory:

firesync apply --env=prod --schema-dir=custom_schemas

Note: Apply operations are idempotent and skip existing resources. Delete operations are not implemented for safety.

Workspace Configuration

FireSync uses config.yaml to manage multiple environments with separate schemas.

Workspace Structure

your-project/
โ”œโ”€โ”€ config.yaml                    # Workspace configuration
โ”œโ”€โ”€ firestore_schema/              # Schema directories per environment
โ”‚   โ”œโ”€โ”€ dev/
โ”‚   โ”‚   โ”œโ”€โ”€ composite-indexes.json
โ”‚   โ”‚   โ”œโ”€โ”€ field-indexes.json
โ”‚   โ”‚   โ””โ”€โ”€ ttl-policies.json
โ”‚   โ”œโ”€โ”€ staging/
โ”‚   โ”‚   โ”œโ”€โ”€ composite-indexes.json
โ”‚   โ”‚   โ”œโ”€โ”€ field-indexes.json
โ”‚   โ”‚   โ””โ”€โ”€ ttl-policies.json
โ”‚   โ””โ”€โ”€ prod/
โ”‚       โ”œโ”€โ”€ composite-indexes.json
โ”‚       โ”œโ”€โ”€ field-indexes.json
โ”‚       โ””โ”€โ”€ ttl-policies.json
โ””โ”€โ”€ secrets/                       # GCP service account keys (gitignored)
    โ”œโ”€โ”€ gcp-key-dev.json
    โ”œโ”€โ”€ gcp-key-staging.json
    โ””โ”€โ”€ gcp-key-prod.json

config.yaml Format

version: 1
schema_base_dir: firestore_schema

environments:
  dev:
    key_path: secrets/gcp-key-dev.json        # Direct path to key file
    description: "Development environment"
  prod:
    key_env: GCP_PROD_KEY                     # Env var (JSON content OR file path)
    description: "Production environment"

Fields:

  • version - Config format version (always 1)
  • schema_base_dir - Base directory for all schema files
  • environments - Map of environment configurations (choose ONE per environment):
    • key_path - Direct path to GCP service account key file (relative to config.yaml)
    • key_env - Environment variable name (auto-detects JSON content or file path)
    • description - Optional description of the environment

Usage Examples

Basic Workflow

# 1. Initialize workspace
firesync init

# 2. Add environment
firesync env add dev --key-path=./secrets/gcp-key-dev.json --description="Development environment"

# 3. Pull current schema
firesync pull --env=dev

# 4. Edit schema files (add/modify indexes)
vim firestore_schema/dev/composite-indexes.json

# 5. Preview changes
firesync plan --env=dev

# 6. Apply changes
firesync apply --env=dev

# 7. Commit to git
git add config.yaml firestore_schema/
git commit -m "Add user query index"

Multi-Environment Management

Manage dev, staging, and production environments separately:

# Set up all environments
firesync init
firesync env add dev --key-path=./secrets/gcp-key-dev.json --description="Development environment"
firesync env add staging --key-path=./secrets/gcp-key-staging.json --description="Staging environment"
firesync env add prod --key-env=GCP_PROD_KEY --description="Production environment"

# Pull schemas from all environments
firesync pull --all

# Work on dev environment
vim firestore_schema/dev/composite-indexes.json
firesync plan --env=dev
firesync apply --env=dev

# Promote dev schema to staging
# --env-from: source environment (reads LOCAL schema files from firestore_schema/dev/)
# --env-to: target environment (applies to REMOTE Firestore in staging project)
firesync plan --env-from=dev --env-to=staging
firesync apply --env-from=dev --env-to=staging

# IMPORTANT: Local files for staging are NOT updated automatically!
# Pull staging schema to update local files after migration:
firesync pull --env=staging

# After testing, promote to production
firesync plan --env-from=dev --env-to=prod
firesync apply --env-from=dev --env-to=prod

# Update production local files
firesync pull --env=prod

Environment Migration

Compare and migrate schemas between environments:

# Compare dev and staging schemas (local vs local)
firesync plan --env-from=dev --env-to=staging

# Apply dev schema to staging environment
firesync apply --env-from=dev --env-to=staging

# Verify changes
firesync pull --env=staging
git diff firestore_schema/staging/

Configuration

Custom Schema Directory

Override the schema directory for any command:

firesync plan --env=dev --schema-dir=custom_schemas
firesync apply --env=staging --schema-dir=backups/schemas_2024

Schema Files

FireSync manages three types of Firestore configurations:

composite-indexes.json

Composite indexes for complex queries spanning multiple fields. Each index includes metadata like name, state, and density from Firestore.

Example:

[
  {
    "density": "SPARSE_ALL",
    "fields": [
      {
        "fieldPath": "userId",
        "order": "ASCENDING"
      },
      {
        "fieldPath": "status",
        "order": "ASCENDING"
      },
      {
        "fieldPath": "createdAt",
        "order": "DESCENDING"
      },
      {
        "fieldPath": "__name__",
        "order": "DESCENDING"
      }
    ],
    "name": "projects/your-project-id/databases/(default)/collectionGroups/orders/indexes/CICAgICAgICA",
    "queryScope": "COLLECTION",
    "state": "READY"
  }
]

Note: When creating new indexes manually, you only need to specify fields and queryScope. The name, state, and density fields are populated by Firestore.

field-indexes.json

Single-field index configurations including default index settings and exemptions.

Example:

[
  {
    "indexConfig": {
      "indexes": [
        {
          "fields": [
            {
              "fieldPath": "*",
              "order": "ASCENDING"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        },
        {
          "fields": [
            {
              "fieldPath": "*",
              "order": "DESCENDING"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        },
        {
          "fields": [
            {
              "arrayConfig": "CONTAINS",
              "fieldPath": "*"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        }
      ]
    },
    "name": "projects/your-project-id/databases/(default)/collectionGroups/__default__/fields/*"
  }
]

Note: The __default__/fields/* entry controls default indexing behavior for all collections.

ttl-policies.json

Time-to-live policies for automatic document deletion. Documents are deleted when the TTL field value expires.

Example:

[
  {
    "indexConfig": {
      "ancestorField": "projects/your-project-id/databases/(default)/collectionGroups/__default__/fields/*",
      "indexes": [
        {
          "fields": [
            {
              "fieldPath": "expiresAt",
              "order": "ASCENDING"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        },
        {
          "fields": [
            {
              "fieldPath": "expiresAt",
              "order": "DESCENDING"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        },
        {
          "fields": [
            {
              "arrayConfig": "CONTAINS",
              "fieldPath": "expiresAt"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        }
      ],
      "usesAncestorConfig": true
    },
    "name": "projects/your-project-id/databases/(default)/collectionGroups/sessions/fields/expiresAt",
    "ttlConfig": {
      "state": "ACTIVE"
    }
  }
]

Note: TTL policies automatically create necessary indexes for the TTL field (ASCENDING, DESCENDING, and CONTAINS). The ttlConfig.state can be ACTIVE or INACTIVE.

Project Structure

Your workspace (after firesync init):

your-project/
โ”œโ”€โ”€ config.yaml                # Workspace configuration
โ”œโ”€โ”€ firestore_schema/          # Schema directories (per environment)
โ”‚   โ”œโ”€โ”€ dev/
โ”‚   โ”‚   โ”œโ”€โ”€ composite-indexes.json
โ”‚   โ”‚   โ”œโ”€โ”€ field-indexes.json
โ”‚   โ”‚   โ””โ”€โ”€ ttl-policies.json
โ”‚   โ”œโ”€โ”€ staging/
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ””โ”€โ”€ prod/
โ”‚       โ””โ”€โ”€ ...
โ””โ”€โ”€ secrets/                   # GCP service account keys (gitignored)
    โ”œโ”€โ”€ gcp-key-dev.json
    โ”œโ”€โ”€ gcp-key-staging.json
    โ””โ”€โ”€ gcp-key-prod.json

Source code structure (for contributors):

firesync/
โ”œโ”€โ”€ src/firesync/              # Main Python package
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ __main__.py            # python -m firesync support
โ”‚   โ”œโ”€โ”€ main.py                # CLI entry point
โ”‚   โ”œโ”€โ”€ cli.py                 # CLI argument parsing
โ”‚   โ”œโ”€โ”€ config.py              # Configuration management
โ”‚   โ”œโ”€โ”€ gcloud.py              # GCloud CLI wrapper
โ”‚   โ”œโ”€โ”€ normalizers.py         # Data normalization
โ”‚   โ”œโ”€โ”€ operations.py          # Resource operations
โ”‚   โ”œโ”€โ”€ schema.py              # Schema file handling
โ”‚   โ”œโ”€โ”€ workspace.py           # Workspace configuration
โ”‚   โ””โ”€โ”€ commands/              # Command implementations
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ”œโ”€โ”€ init.py
โ”‚       โ”œโ”€โ”€ env.py
โ”‚       โ”œโ”€โ”€ pull.py
โ”‚       โ”œโ”€โ”€ plan.py
โ”‚       โ””โ”€โ”€ apply.py
โ”œโ”€โ”€ tests/                     # Unit tests
โ”‚   โ”œโ”€โ”€ test_cli.py
โ”‚   โ”œโ”€โ”€ test_config.py
โ”‚   โ”œโ”€โ”€ test_gcloud.py
โ”‚   โ”œโ”€โ”€ test_normalizers.py
โ”‚   โ”œโ”€โ”€ test_operations.py
โ”‚   โ”œโ”€โ”€ test_schema.py
โ”‚   โ””โ”€โ”€ test_workspace.py
โ”œโ”€โ”€ pyproject.toml             # Package configuration
โ”œโ”€โ”€ LICENSE
โ””โ”€โ”€ README.md

CI/CD Integration

GitHub Actions - Single Environment

name: Deploy Firestore Schema

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'

      - name: Install firesync
        run: pip install firestore-schema-migration

      - name: Install gcloud CLI
        uses: google-github-actions/setup-gcloud@v1

      - name: Create config.yaml
        run: |
          echo "version: 1" > config.yaml
          echo "schema_base_dir: firestore_schema" >> config.yaml
          echo "environments:" >> config.yaml
          echo "  prod:" >> config.yaml
          echo "    key_env: GCP_KEY" >> config.yaml
          echo "    schema_dir: prod" >> config.yaml

      - name: Plan schema changes
        env:
          GCP_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
        run: firesync plan --env=prod

      - name: Apply schema changes
        env:
          GCP_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
        run: firesync apply --env=prod

GitHub Actions - Multi-Environment Pipeline

name: Deploy to All Environments

on:
  push:
    branches: [main]

jobs:
  deploy-dev:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install firestore-schema-migration
      - uses: google-github-actions/setup-gcloud@v1

      - name: Deploy to dev
        env:
          GCP_DEV_KEY: ${{ secrets.GCP_DEV_KEY }}
        run: |
          firesync plan --env=dev
          firesync apply --env=dev

  deploy-staging:
    needs: deploy-dev
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install firestore-schema-migration
      - uses: google-github-actions/setup-gcloud@v1

      - name: Deploy to staging
        env:
          GCP_STAGING_KEY: ${{ secrets.GCP_STAGING_KEY }}
        run: |
          firesync plan --env=staging
          firesync apply --env=staging

  deploy-prod:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install firestore-schema-migration
      - uses: google-github-actions/setup-gcloud@v1

      - name: Deploy to production
        env:
          GCP_PROD_KEY: ${{ secrets.GCP_PROD_KEY }}
        run: |
          firesync plan --env=prod
          firesync apply --env=prod

Limitations

  • Delete operations: Plan shows deletions but Apply doesn't implement them (safety feature)
  • Manual schema editing: Schema files must be edited manually or pulled from existing Firestore
  • Single region: Assumes single Firestore region per project

Troubleshooting

Authentication Issues

# Verify gcloud is installed
gcloud --version

# Check active account
gcloud auth list

# Re-authenticate if needed
gcloud auth login

Permission Denied

Ensure your service account has these IAM roles:

  • roles/datastore.indexAdmin or
  • roles/datastore.owner

Schema Files Not Found

# Pull schema first if firestore_schema/ is empty
firesync pull --env=dev

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

MIT License - see LICENSE for details.

Author

Pavel Ravvich


Note: Always test schema changes in development/staging environments before applying to production.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

firestore_schema_migration-0.1.3.tar.gz (34.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

firestore_schema_migration-0.1.3-py3-none-any.whl (31.8 kB view details)

Uploaded Python 3

File details

Details for the file firestore_schema_migration-0.1.3.tar.gz.

File metadata

File hashes

Hashes for firestore_schema_migration-0.1.3.tar.gz
Algorithm Hash digest
SHA256 ef15dbc8a5f185c81546aea4f7b30e5090d963640149740290c44f1017e418ff
MD5 2f5f9c21ad7378d541c4f63d3c702236
BLAKE2b-256 ca4cd1ba889c4d7b8c64c980c3bcdc89a3438efea87d112c591f315edfcb4405

See more details on using hashes here.

File details

Details for the file firestore_schema_migration-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for firestore_schema_migration-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c4cdb10c51e3584b8aa2b9c80acca703947ca344e5cb7519dab22b4950ea05ec
MD5 c64e5688e54944e7afe1559fb07e04b2
BLAKE2b-256 c3181324960c4c42a758c7acb7087d6dc03fbbbcbb317a8ea0b7741307a14cdf

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page