Expanding CI/CD to Database and Infrastructure: A Practical Roadmap
Your application pipeline is running smoothly. Code changes flow from commit to production with automated tests, builds, and deployments. The team feels confident pushing updates. But there's a problem: database schema changes still happen through someone running SQL scripts directly on production, and infrastructure is configured by logging into cloud consoles and clicking buttons.
This gap creates friction. A developer adds a new column to a table, but the migration script is emailed to a DBA who runs it manually. A server needs a configuration change, but someone has to SSH in and edit files. These manual steps break the consistency you've built everywhere else. They introduce risk, lack audit trails, and make rollbacks nearly impossible.
The good news is that the same pipeline principles apply to databases and infrastructure. The difference is in the details: what you test, how you manage risk, and how you handle rollbacks.
Bringing Database Migrations Into the Pipeline
Database changes are different from application code because they operate on live data. A bug in application code might cause errors that are easy to fix. A bad migration can corrupt or delete data permanently. This reality makes teams hesitant to automate database deployments, but the alternative-manual execution-is worse.
Start by treating every schema change as code. Each migration should be a script stored in version control, either in the same repository as your application or in a dedicated one. The script should have a clear version, a description of what it does, and a corresponding rollback script.
For example, a migration to add a column might look like this:
-- V002__add_status_column.sql
-- Forward migration: add a status column with a default value
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';
-- V002__add_status_column_rollback.sql
-- Rollback migration: remove the status column
ALTER TABLE users DROP COLUMN status;
The following diagram illustrates how database and infrastructure stages fit into your existing pipeline:
Your pipeline then runs these migrations automatically across environments: development, staging, and production. But because the stakes are higher, you need additional gates:
- Dry runs in staging: Before running a migration in production, execute it in staging with data volume that mirrors production. This catches performance issues and unexpected locking behavior.
- Compatibility tests: Check whether the migration works with existing data. A migration that adds a NOT NULL column without a default value will fail if existing rows have NULL values in that column.
- Approval gates for destructive changes: Dropping a column, changing a data type, or removing a table should require manual approval. These changes are harder or impossible to reverse.
Every migration must have a tested rollback script. Adding a column is usually safe to reverse. Dropping a table is not. Your pipeline should be able to execute the rollback automatically if the migration fails or if the team decides to revert.
Infrastructure as Code: Declare, Not Click
Infrastructure includes servers, networks, load balancers, database instances, and every component your application needs to run. When you provision these manually through cloud consoles, you create several problems:
- Environments drift apart. Development might have slightly different settings than production.
- Changes are hard to reproduce. If a server crashes, can you recreate it exactly?
- There's no audit trail. Who changed the firewall rules last week?
The solution is to declare your infrastructure as code. Write configuration files that describe how your infrastructure should look, then let the pipeline apply those configurations. This is infrastructure as code (IaC).
Tools like Terraform, AWS CloudFormation, or Pulumi let you define resources in files. These files go through the same pipeline as your application code: they enter a repository, undergo code review, get tested in development, and then apply to staging and production.
Testing infrastructure is different from testing applications. Application tests check whether code behaves correctly. Infrastructure tests check whether the configuration produces the expected environment:
- Are the required ports open?
- Is the firewall configured correctly?
- Is the operating system version correct?
- Are the resource sizes (CPU, memory, disk) as specified?
Some teams go further by provisioning infrastructure in an isolated environment, running tests against it, then destroying it. This gives high confidence before applying changes to production.
Risk gates for infrastructure follow the same pattern as databases. A change that adjusts disk size might only need automated tests. A change that restructures the network or replaces a database type needs manual approval and more thorough validation.
Rollback Strategies for Database and Infrastructure
Rolling back application code is usually straightforward: deploy the previous version. Rolling back a database migration or infrastructure change requires more planning.
For database migrations, the rollback script must be written at the same time as the forward migration. Test the rollback in your pipeline, not just in your head. Some migrations are additive (adding a column) and roll back cleanly. Others are transformative (splitting a table) and require careful rollback logic. If a migration cannot be safely rolled back, that's a signal to redesign the migration approach.
For infrastructure, your pipeline should store the previous state of your configuration. When you need to roll back, the pipeline applies the previous configuration. This works well for declarative IaC tools because they handle the diff between current and desired state automatically.
Practical Checklist for Expanding Your Pipeline
| Area | What to Add | Risk Gate | Rollback |
|---|---|---|---|
| Database | Migration scripts in version control | Dry run in staging, approval for destructive changes | Tested rollback script per migration |
| Infrastructure | IaC configuration files | Automated validation, approval for architecture changes | Previous configuration stored and deployable |
The Pattern Repeats
Once you have database and infrastructure in your pipeline, you'll notice the same pattern applies to other areas: environment configuration, feature flags, mobile app releases. The specifics change-what you test, how you manage risk, how you roll back-but the core idea stays the same. Every change follows a consistent, tested, reversible path.
Start with one database migration in your pipeline. Then one infrastructure change. The first few will feel slow because you're building the process. But each one makes the next faster and safer.
The goal is not automation for its own sake. It's making sure that when something breaks-and it will-you know exactly what changed, how to fix it, and how to prevent it from happening again.