DEV Community

Cover image for Automating MySQL Database Migrations in AWS CI/CD using Knex.js and TypeScript
Aman Kumar
Aman Kumar

Posted on

Automating MySQL Database Migrations in AWS CI/CD using Knex.js and TypeScript

Managing database schema changes across environments (Development, Staging, Production) can quickly become a bottleneck if done manually. Automating schema migrations inside your AWS deployment pipeline ensures that database updates are applied consistently, safely, and in perfect sync with your application code.

In this guide, we will walk through setting up automated MySQL migrations in AWS using Knex.js, TypeScript, and AWS CodeBuild.

Architecture & Pipeline Flow

The goal is to execute schema migrations automatically during the build/deployment phase. The workflow runs in the following order:

Knex handles schema version control by maintaining a knex_migrations table inside your target MySQL database. Every migration file is tracked with a timestamp; Knex runs only pending scripts, preventing duplicate executions.

Step 1: Project Configuration & Dependencies

Install Knex, the MySQL client driver, and TypeScript execution tools:

Bash

npm install knex mysql2
npm install --save-dev typescript ts-node @types/node
Enter fullscreen mode Exit fullscreen mode

Update your package.json to include dedicated migration scripts:

JSON

{
  "scripts": {
    "db:migrate": "knex --knexfile src/knexfile.ts migrate:latest",
    "db:rollback": "knex --knexfile src/knexfile.ts migrate:rollback",
    "db:make-migration": "knex --knexfile src/knexfile.ts migrate:make"
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Setting Up knexfile.ts

Create a knexfile.ts in your project root or src/ directory. This file configures the connection parameters and specifies where migration files live.

TypeScript

import type { Knex } from 'knex';

const config: { [key: string]: Knex.Config } = {
  production: {
    client: 'mysql2',
    connection: {
      host: process.env.DB_HOST,
      port: Number(process.env.DB_PORT) || 3306,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: true } : false,
    },
    migrations: {
      tableName: 'knex_migrations',
      directory: './migrations',
      extension: 'ts',
    },
    pool: {
      min: 2,
      max: 10,
    },
  },
};

export default config;
Enter fullscreen mode Exit fullscreen mode

Step 3: Writing Idempotent Migration Scripts

To ensure safety in production environments, migrations should be idempotent (safe to run even if partially executed or interrupted) and always include a downfunction for rollback capability. Use Knex’s schema helper methods like hasTableand hasColumnto prevent accidental runtime errors.

Create a new migration script using:

Bash

npm run db:make-migration add_status_to_users
Enter fullscreen mode Exit fullscreen mode

Edit the generated migration file:

TypeScript
import { Knex } from 'knex';

export async function up(knex: Knex): Promise<void> {
  const hasTable = await knex.schema.hasTable('users');

  if (hasTable) {
    const hasColumn = await knex.schema.hasColumn('users', 'status');
    if (!hasColumn) {
      await knex.schema.alterTable('users', (table) => {
        table.string('status', 20).defaultTo('ACTIVE').notNullable().index();
      });
    }
  }
}

export async function down(knex: Knex): Promise<void> {
  const hasTable = await knex.schema.hasTable('users');

  if (hasTable) {
    const hasColumn = await knex.schema.hasColumn('users', 'status');
    if (hasColumn) {
      await knex.schema.alterTable('users', (table) => {
        table.dropColumn('status');
      });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: AWS CI/CD Pipeline Integration

To run migrations seamlessly in your AWS deployment script (deploy.sh), encapsulate the migration execution inside a dedicated shell function. Place this step after unit tests pass and before updating application services (e.g., ECS, Lambda, or EC2 instances).

deploy.shsnippet
Bash

#!/usr/bin/env bash
set -e

# 1. Run Unit Tests
echo "Running unit tests..."
npm test

# 2. Migration Function
migrateBackend() {
  echo "Starting MySQL database migration via Knex..."

  # Ensure DB credentials are exported in environment
  export NODE_ENV=production

  if npm run db:migrate; then
    echo "Database migration completed successfully."
  else
    echo "Database migration failed! Initiating rollback..."
    npm run db:rollback || true
    exit 1
  fi
}

# Run database migration
migrateBackend

# 3. Deploy Application Code
echo "Deploying application service..."
# aws ecs update-service --cluster my-cluster --service my-service --force-new-deployment
Enter fullscreen mode Exit fullscreen mode

Step 5: AWS CodeBuild Configuration

In your buildspec.yml, execute deploy.sh during the buildor post_build phase. Ensure that database environment variables are injected securely using AWS Secrets Manager or Systems Manager (SSM) Parameter Store.

YAML

version: 0.2

env:
  secrets-manager:
    DB_HOST: "production/db/credentials:host"
    DB_USER: "production/db/credentials:username"
    DB_PASSWORD: "production/db/credentials:password"
    DB_NAME: "production/db/credentials:dbname"
  variables:
    DB_PORT: "3306"
    DB_SSL: "true"

phases:
  install:
    runtime-versions:
      nodejs: 20
    commands:
      - npm ci
  build:
    commands:
      - chmod +x ./deploy.sh
      - ./deploy.sh
Enter fullscreen mode Exit fullscreen mode

Key Takeaways & Best Practices

1. Test Rollbacks: Periodically verify that your down migration functions run correctly by simulating failures in lower environments.

2. Never Modify Existing Migrations:
Once a migration script has executed in Staging or Production, do not alter its contents. Always create a new migration file to fix or alter schema attributes.

3. Keep Schema Checks Defensive: Using checks like hasColumn and hasTable prevents pipeline failures caused by out-of-sync developer environments.

4. Isolate Database Permissions: The CI/CD database user should have DDL permissions (CREATE, ALTER, DROP) restricted to the target schema and require SSL connections.

Top comments (0)