This tutorial is part of the Manage Bytebase with Terraform series:

  • Part 1: Manage Databases with Terraform (This one) - Set up instances and environments
  • Part 2: Manage Projects with Terraform - Organize databases into projects
  • Part 3: Configure Access Controls with Terraform (Coming next) - Set up IAM and permissions

📚 Complete examples: GitHub - Bytebase Terraform Provider

Learn how to automate database infrastructure management by combining Bytebase with Terraform using the Terraform Bytebase Provider.

What You’ll Learn

In this tutorial, you’ll discover how to:

  • Set up Bytebase and Terraform for database infrastructure management
  • Configure environments using two different approaches (settings vs. individual resources)
  • Create and manage database instances across multiple environments
  • Implement proper dependency management for reliable deployments

Why Use Terraform with Bytebase?

While Bytebase provides an intuitive GUI for database management, Terraform brings several key advantages for larger deployments:

  • Scale Efficiently: Manage hundreds of database instances without repetitive manual work
  • Reduce Errors: Infrastructure as code eliminates human mistakes in configuration
  • Version Control: Track and review all infrastructure changes through Git workflows
  • Consistency: Ensure identical configurations across environments

Bytebase Terraform Provider handles control plane configuration such as settings, policies, access controls. It does not handle data plane operations such as database creation, schema migrations, DML execution, query.

Prerequisites

Before starting this tutorial, ensure you have:

  • Docker: Install Docker to run Bytebase and MySQL instances
  • Terraform: We’ll install this in the next section
  • Basic CLI Knowledge: Familiarity with terminal/command line operations

Setup

Install Terraform

For macOS (using Homebrew):

# Install HashiCorp tap
brew tap hashicorp/tap

# Install Terraform
brew install hashicorp/tap/terraform

# Verify installation
terraform -help

For other platforms: Follow the official Terraform installation guide.

Start Services

  1. Start Bytebase:

    docker run --rm --init \
      --name bytebase \
      --publish 8080:8080 --pull always \
      --volume ~/.bytebase/data:/var/opt/bytebase \
      bytebase/bytebase:3.8.0
    
  2. Start MySQL instances for Test and Prod environments:

    # Test environment MySQL (port 3307)
    docker run --name mysqldtest \
      -p 3307:3306 \
      -e MYSQL_ROOT_PASSWORD=testpwd1 \
      -d mysql:8.0
    
    # Production environment MySQL (port 3308)
    docker run --name mysqldprod \
      -p 3308:3306 \
      -e MYSQL_ROOT_PASSWORD=testpwd1 \
      -d mysql:8.0
    
  3. Set up Bytebase admin account:

    Navigate to Bytebase (usually http://localhost:8080) and register an admin account with Workspace Admin role.

Initial Bytebase Setup (GUI)

Before automating with Terraform, let’s set up a basic instance through the GUI to understand the manual process:

  1. Add a database instance:

    • Login as admin, click Instances on the left sidebar, click + Add Instance
    • Configure for Test environment with credentials root/testpwd1

  2. Create a project and database:

    • Click Projects on the left sidebar, click + New Project, create project Test
    • Navigate to project, click Database > Databases on the left sidebar, click + New DB
    • Create database demo

This manual process works fine for a few instances, but imagine doing this for dozens or hundreds of database instances across multiple environments!

Add Instances via Terraform

You’ve added an instance for the Test environment in Bytebase by clicking. What if you need to add hundreds of instances. In this section, you’ll witness the process simplification Terraform brings.

Step 1 - Create a Terraform file

  1. Create a new folder learn-terraform-bytebase and create a blank file main.tf in it.

  2. Go to https://registry.terraform.io/providers/bytebase/bytebase/latest/docs. Click Use Provider, copy and paste the whole code block in the gray box into main.tf. Pay attention to the version.

  3. Copy the following provider part and paste it in main.tf.

    provider "bytebase" {
       service_account = "<Your Bytebase service account email>"
       service_key     = "<Your Bytebase service account key>"
       url             = "<Your Bytebase external URL>"
    }
    

Step 2 - Add a Terraform account

  1. Go to IAM & Admin > Users & Groups, click + Add User.

  2. Choose Service Account as the Type, fill in the Email with tf@service.bytebase.com, choose Workspace DBA as Roles, and click Confirm.

  3. Copy the Service Key for later use.

Step 3 - Query to list all resources

  1. Paste the Service Key, Service Account Email, and URL into main.tf.

  2. Paste the following queries after the provider block and save the file. What it does is to list all existing environments and instances and print those out in the terminal.

    # List all environments using settings
    data "bytebase_setting" "environments" {
       name = "settings/ENVIRONMENT"
    }
    output "all_environments" {
       value = data.bytebase_setting.environments
    }
    
    # List all instances
    data "bytebase_instance_list" "all" {}
    output "all_instances" {
       value = data.bytebase_instance_list.all
    }
    
  3. Run terraform init, terraform plan and terraform apply one by one in the terminal. You’ll see the output like this:

    all_environments = {
       "approval_flow" = tolist(null) /* of object */
       "classification" = tolist(null) /* of object */
       "environment_setting" = tolist([
         {
             "environment" = tolist([
             {
               "color" = ""
               "id" = "test"
               "name" = "environments/test"
               "protected" = true
               "title" = "Test"
             },
             {
               "color" = ""
               "id" = "prod"
               "name" = "environments/prod"
               "protected" = true
               "title" = "Prod"
             },
             ])
         },
       ])
       "id" = "settings/ENVIRONMENT"
       "name" = "settings/ENVIRONMENT"
       "semantic_types" = toset([])
       "workspace_profile" = tolist(null) /* of object */
    }
    

    As we have two default environments in our Bytebase. Pay attention to resource_id, they are test and prod .

    all_instances = {
       "id" = "1744624330"
       "instances" = tolist([
           {
             "activation" = false
             "data_sources" = toset([
             {
                 "database" = ""
                 "external_secret" = tolist([])
                 "host" = "host.docker.internal"
                 "id" = "bb67b4b8-40c6-4ac6-a170-5f673183c759"
                 "password" = ""
                 "port" = "3307"
                 "ssl_ca" = ""
                 "ssl_cert" = ""
                 "ssl_key" = ""
                 "type" = "ADMIN"
                 "username" = "root"
             },
             ])
             "engine" = "MYSQL"
             "engine_version" = "8.0.36"
             "environment" = "environments/test"
             "external_link" = ""
             "maximum_connections" = 0
             "name" = "instances/mysql-test"
             "resource_id" = "mysql-test"
             "sync_interval" = 0
             "title" = "MySQL test"
           },
           ...
       ])
       "show_deleted" = false
    }
    

    As we can see, it’s the instance we just added. Follow "title" = "MySQL test", you’ll find "resource_id" = "mysql-test".

Step 4 - Configure Environments

Before creating instances, let’s properly configure the environments. There are two approaches to achieve this - you typically only need one of them.

This approach uses environment settings and is usually sufficient for most use cases.

  1. Remove the #List all environment and #List all instances blocks, and add the following environment configuration:

    # Define local variables for environment IDs
    locals {
       environment_id_test = "test"
       environment_id_prod = "prod"
    }
    
    # Configure environment settings
    resource "bytebase_setting" "environments" {
       name = "settings/ENVIRONMENT"
    
       environment_setting {
         environment {
           id        = local.environment_id_test
           title     = "Test"
           protected = false
         }
    
         environment {
           id        = local.environment_id_prod
           title     = "Prod"
           protected = true
         }
      }
    }
    

Step 4b - Create Environments via Individual Resources (Alternative)

Alternatively, you can create environments using individual bytebase_environment resources. This approach provides more granular control but requires careful dependency management.

Important: When using multiple bytebase_environment resources, you must use depends_on between environments. This ensures Terraform updates them in the correct order, as the Bytebase API only supports updating one environment list at a time.

  1. Add the following environment resources after the settings configuration:

    # Create Test environment
    resource "bytebase_environment" "test" {
      resource_id             = local.environment_id_test
      title                   = "Test"
      order                   = 0
      protected               = false
    }
    
    # Create Production environment
    # depends_on ensures environments are created in sequence
    # This prevents API conflicts when updating the environment list
    resource "bytebase_environment" "prod" {
      depends_on              = [bytebase_environment.test]
      resource_id             = local.environment_id_prod
      title                   = "Prod"
      order                   = 1
      protected               = true
    }
    

Step 5 - Add Database Instances

Finally, let’s add the database instances that will be associated with our environments. The configuration depends on which approach you chose in Step 4:

If you used Step 4a (Environment Settings)

resource "bytebase_instance" "test" {
  depends_on = [
    bytebase_setting.environments
  ]
  resource_id = "mysql-test"
  environment = "environments/${local.environment_id_test}"
  title       = "MySQL test"
  engine      = "MYSQL"
  activation  = false

  data_sources {
    id       = "admin data source mysql-test"
    type     = "ADMIN"
    host     = "host.docker.internal"
    port     = "3307"
    username = "root"
    password = "testpwd1"
  }
}

resource "bytebase_instance" "prod" {
  depends_on = [
    bytebase_setting.environments
  ]
  resource_id = "mysql-prod"
  environment = "environments/${local.environment_id_prod}"
  title       = "MySQL prod"
  engine      = "MYSQL"
  activation  = false

  data_sources {
    id       = "admin data source mysql-prod"
    type     = "ADMIN"
    host     = "host.docker.internal"
    port     = "3308"
    username = "root"
    password = "testpwd1"
  }
}

If you used Step 4b (Individual Environment Resources)

resource "bytebase_instance" "test" {
  depends_on = [
    bytebase_environment.test
  ]
  resource_id = "mysql-test"
  environment = bytebase_environment.test.name
  title       = "MySQL test"
  engine      = "MYSQL"
  activation  = false

  data_sources {
    id       = "admin data source mysql-test"
    type     = "ADMIN"
    host     = "host.docker.internal"
    port     = "3307"
    username = "root"
    password = "testpwd1"
  }
}

resource "bytebase_instance" "prod" {
  depends_on = [
    bytebase_environment.prod
  ]
  resource_id = "mysql-prod"
  environment = bytebase_environment.prod.name
  title       = "MySQL prod"
  engine      = "MYSQL"
  activation  = false

  data_sources {
    id       = "admin data source mysql-prod"
    type     = "ADMIN"
    host     = "host.docker.internal"
    port     = "3308"
    username = "root"
    password = "testpwd1"
  }
}

Key Points:

  • Step 4a approach: Instances depend on bytebase_setting.environments and use environment references like "environments/${local.environment_id_test}"
  • Step 4b approach: Instances depend on individual environment resources and use direct references like bytebase_environment.test.name
  • Both approaches create the same end result - database instances properly linked to their environments
  1. Run terraform plan and terraform apply one by one in the terminal. You will see this in the terminal.

  2. Go back to Bytebase, and click Environments. There is nothing changed with these two environments.

  3. Click Instances on the left sidebar, and you will see the two instances we just added.

  4. Click into one instance, scroll down and click Test Connection. It should be successful.

Summary and Next

Now you have learned how to use Terraform to manage your MySQL database environments and instances in Bytebase, for PostgreSQL, you can futher declare database roles. Please check more example usage in GitHub.

Next in the Series

Continue to the next tutorial: Manage Projects with Terraform where you’ll learn how to organize your databases into projects for better management.

If you encounter any problems while trying, welcome to our discord channel.

This tutorial is part of the Manage Bytebase with Terraform series:

  • Part 1: Manage Databases with Terraform (This one) - Set up instances and environments
  • Part 2: Manage Projects with Terraform - Organize databases into projects
  • Part 3: Configure Access Controls with Terraform (Coming next) - Set up IAM and permissions

📚 Complete examples: GitHub - Bytebase Terraform Provider

Learn how to automate database infrastructure management by combining Bytebase with Terraform using the Terraform Bytebase Provider.

What You’ll Learn

In this tutorial, you’ll discover how to:

  • Set up Bytebase and Terraform for database infrastructure management
  • Configure environments using two different approaches (settings vs. individual resources)
  • Create and manage database instances across multiple environments
  • Implement proper dependency management for reliable deployments

Why Use Terraform with Bytebase?

While Bytebase provides an intuitive GUI for database management, Terraform brings several key advantages for larger deployments:

  • Scale Efficiently: Manage hundreds of database instances without repetitive manual work
  • Reduce Errors: Infrastructure as code eliminates human mistakes in configuration
  • Version Control: Track and review all infrastructure changes through Git workflows
  • Consistency: Ensure identical configurations across environments

Bytebase Terraform Provider handles control plane configuration such as settings, policies, access controls. It does not handle data plane operations such as database creation, schema migrations, DML execution, query.

Prerequisites

Before starting this tutorial, ensure you have:

  • Docker: Install Docker to run Bytebase and MySQL instances
  • Terraform: We’ll install this in the next section
  • Basic CLI Knowledge: Familiarity with terminal/command line operations

Setup

Install Terraform

For macOS (using Homebrew):

# Install HashiCorp tap
brew tap hashicorp/tap

# Install Terraform
brew install hashicorp/tap/terraform

# Verify installation
terraform -help

For other platforms: Follow the official Terraform installation guide.

Start Services

  1. Start Bytebase:

    docker run --rm --init \
      --name bytebase \
      --publish 8080:8080 --pull always \
      --volume ~/.bytebase/data:/var/opt/bytebase \
      bytebase/bytebase:3.8.0
    
  2. Start MySQL instances for Test and Prod environments:

    # Test environment MySQL (port 3307)
    docker run --name mysqldtest \
      -p 3307:3306 \
      -e MYSQL_ROOT_PASSWORD=testpwd1 \
      -d mysql:8.0
    
    # Production environment MySQL (port 3308)
    docker run --name mysqldprod \
      -p 3308:3306 \
      -e MYSQL_ROOT_PASSWORD=testpwd1 \
      -d mysql:8.0
    
  3. Set up Bytebase admin account:

    Navigate to Bytebase (usually http://localhost:8080) and register an admin account with Workspace Admin role.

Initial Bytebase Setup (GUI)

Before automating with Terraform, let’s set up a basic instance through the GUI to understand the manual process:

  1. Add a database instance:

    • Login as admin, click Instances on the left sidebar, click + Add Instance
    • Configure for Test environment with credentials root/testpwd1

  2. Create a project and database:

    • Click Projects on the left sidebar, click + New Project, create project Test
    • Navigate to project, click Database > Databases on the left sidebar, click + New DB
    • Create database demo

This manual process works fine for a few instances, but imagine doing this for dozens or hundreds of database instances across multiple environments!

Add Instances via Terraform

You’ve added an instance for the Test environment in Bytebase by clicking. What if you need to add hundreds of instances. In this section, you’ll witness the process simplification Terraform brings.

Step 1 - Create a Terraform file

  1. Create a new folder learn-terraform-bytebase and create a blank file main.tf in it.

  2. Go to https://registry.terraform.io/providers/bytebase/bytebase/latest/docs. Click Use Provider, copy and paste the whole code block in the gray box into main.tf. Pay attention to the version.

  3. Copy the following provider part and paste it in main.tf.

    provider "bytebase" {
       service_account = "<Your Bytebase service account email>"
       service_key     = "<Your Bytebase service account key>"
       url             = "<Your Bytebase external URL>"
    }
    

Step 2 - Add a Terraform account

  1. Go to IAM & Admin > Users & Groups, click + Add User.

  2. Choose Service Account as the Type, fill in the Email with tf@service.bytebase.com, choose Workspace DBA as Roles, and click Confirm.

  3. Copy the Service Key for later use.

Step 3 - Query to list all resources

  1. Paste the Service Key, Service Account Email, and URL into main.tf.

  2. Paste the following queries after the provider block and save the file. What it does is to list all existing environments and instances and print those out in the terminal.

    # List all environments using settings
    data "bytebase_setting" "environments" {
       name = "settings/ENVIRONMENT"
    }
    output "all_environments" {
       value = data.bytebase_setting.environments
    }
    
    # List all instances
    data "bytebase_instance_list" "all" {}
    output "all_instances" {
       value = data.bytebase_instance_list.all
    }
    
  3. Run terraform init, terraform plan and terraform apply one by one in the terminal. You’ll see the output like this:

    all_environments = {
       "approval_flow" = tolist(null) /* of object */
       "classification" = tolist(null) /* of object */
       "environment_setting" = tolist([
         {
             "environment" = tolist([
             {
               "color" = ""
               "id" = "test"
               "name" = "environments/test"
               "protected" = true
               "title" = "Test"
             },
             {
               "color" = ""
               "id" = "prod"
               "name" = "environments/prod"
               "protected" = true
               "title" = "Prod"
             },
             ])
         },
       ])
       "id" = "settings/ENVIRONMENT"
       "name" = "settings/ENVIRONMENT"
       "semantic_types" = toset([])
       "workspace_profile" = tolist(null) /* of object */
    }
    

    As we have two default environments in our Bytebase. Pay attention to resource_id, they are test and prod .

    all_instances = {
       "id" = "1744624330"
       "instances" = tolist([
           {
             "activation" = false
             "data_sources" = toset([
             {
                 "database" = ""
                 "external_secret" = tolist([])
                 "host" = "host.docker.internal"
                 "id" = "bb67b4b8-40c6-4ac6-a170-5f673183c759"
                 "password" = ""
                 "port" = "3307"
                 "ssl_ca" = ""
                 "ssl_cert" = ""
                 "ssl_key" = ""
                 "type" = "ADMIN"
                 "username" = "root"
             },
             ])
             "engine" = "MYSQL"
             "engine_version" = "8.0.36"
             "environment" = "environments/test"
             "external_link" = ""
             "maximum_connections" = 0
             "name" = "instances/mysql-test"
             "resource_id" = "mysql-test"
             "sync_interval" = 0
             "title" = "MySQL test"
           },
           ...
       ])
       "show_deleted" = false
    }
    

    As we can see, it’s the instance we just added. Follow "title" = "MySQL test", you’ll find "resource_id" = "mysql-test".

Step 4 - Configure Environments

Before creating instances, let’s properly configure the environments. There are two approaches to achieve this - you typically only need one of them.

This approach uses environment settings and is usually sufficient for most use cases.

  1. Remove the #List all environment and #List all instances blocks, and add the following environment configuration:

    # Define local variables for environment IDs
    locals {
       environment_id_test = "test"
       environment_id_prod = "prod"
    }
    
    # Configure environment settings
    resource "bytebase_setting" "environments" {
       name = "settings/ENVIRONMENT"
    
       environment_setting {
         environment {
           id        = local.environment_id_test
           title     = "Test"
           protected = false
         }
    
         environment {
           id        = local.environment_id_prod
           title     = "Prod"
           protected = true
         }
      }
    }
    

Step 4b - Create Environments via Individual Resources (Alternative)

Alternatively, you can create environments using individual bytebase_environment resources. This approach provides more granular control but requires careful dependency management.

Important: When using multiple bytebase_environment resources, you must use depends_on between environments. This ensures Terraform updates them in the correct order, as the Bytebase API only supports updating one environment list at a time.

  1. Add the following environment resources after the settings configuration:

    # Create Test environment
    resource "bytebase_environment" "test" {
      resource_id             = local.environment_id_test
      title                   = "Test"
      order                   = 0
      protected               = false
    }
    
    # Create Production environment
    # depends_on ensures environments are created in sequence
    # This prevents API conflicts when updating the environment list
    resource "bytebase_environment" "prod" {
      depends_on              = [bytebase_environment.test]
      resource_id             = local.environment_id_prod
      title                   = "Prod"
      order                   = 1
      protected               = true
    }
    

Step 5 - Add Database Instances

Finally, let’s add the database instances that will be associated with our environments. The configuration depends on which approach you chose in Step 4:

If you used Step 4a (Environment Settings)

resource "bytebase_instance" "test" {
  depends_on = [
    bytebase_setting.environments
  ]
  resource_id = "mysql-test"
  environment = "environments/${local.environment_id_test}"
  title       = "MySQL test"
  engine      = "MYSQL"
  activation  = false

  data_sources {
    id       = "admin data source mysql-test"
    type     = "ADMIN"
    host     = "host.docker.internal"
    port     = "3307"
    username = "root"
    password = "testpwd1"
  }
}

resource "bytebase_instance" "prod" {
  depends_on = [
    bytebase_setting.environments
  ]
  resource_id = "mysql-prod"
  environment = "environments/${local.environment_id_prod}"
  title       = "MySQL prod"
  engine      = "MYSQL"
  activation  = false

  data_sources {
    id       = "admin data source mysql-prod"
    type     = "ADMIN"
    host     = "host.docker.internal"
    port     = "3308"
    username = "root"
    password = "testpwd1"
  }
}

If you used Step 4b (Individual Environment Resources)

resource "bytebase_instance" "test" {
  depends_on = [
    bytebase_environment.test
  ]
  resource_id = "mysql-test"
  environment = bytebase_environment.test.name
  title       = "MySQL test"
  engine      = "MYSQL"
  activation  = false

  data_sources {
    id       = "admin data source mysql-test"
    type     = "ADMIN"
    host     = "host.docker.internal"
    port     = "3307"
    username = "root"
    password = "testpwd1"
  }
}

resource "bytebase_instance" "prod" {
  depends_on = [
    bytebase_environment.prod
  ]
  resource_id = "mysql-prod"
  environment = bytebase_environment.prod.name
  title       = "MySQL prod"
  engine      = "MYSQL"
  activation  = false

  data_sources {
    id       = "admin data source mysql-prod"
    type     = "ADMIN"
    host     = "host.docker.internal"
    port     = "3308"
    username = "root"
    password = "testpwd1"
  }
}

Key Points:

  • Step 4a approach: Instances depend on bytebase_setting.environments and use environment references like "environments/${local.environment_id_test}"
  • Step 4b approach: Instances depend on individual environment resources and use direct references like bytebase_environment.test.name
  • Both approaches create the same end result - database instances properly linked to their environments
  1. Run terraform plan and terraform apply one by one in the terminal. You will see this in the terminal.

  2. Go back to Bytebase, and click Environments. There is nothing changed with these two environments.

  3. Click Instances on the left sidebar, and you will see the two instances we just added.

  4. Click into one instance, scroll down and click Test Connection. It should be successful.

Summary and Next

Now you have learned how to use Terraform to manage your MySQL database environments and instances in Bytebase, for PostgreSQL, you can futher declare database roles. Please check more example usage in GitHub.

Next in the Series

Continue to the next tutorial: Manage Projects with Terraform where you’ll learn how to organize your databases into projects for better management.

If you encounter any problems while trying, welcome to our discord channel.