Skip to content

Flyway + PostgreSQL + GitLab CI + AWS RDS

One src/ directory imports from three lexicon packages. chant build --lexicon aws produces a CloudFormation template for RDS. chant build --lexicon flyway produces flyway.toml. chant build --lexicon gitlab produces .gitlab-ci.yml. The GitLab pipeline deploys the CF stack then runs Flyway migrations — the DB host and password are resolved at runtime from stack outputs and SSM.

┌─────────────────────────────────────────────────────────────────┐
│ GitLab CI Pipeline (.gitlab-ci.yml) │
│ │
│ deploy-infra: aws cloudformation deploy │
│ → VPC + private subnets + RDS PostgreSQL │
│ → outputs: DbEndpoint │
│ │
│ run-migrations: flyway migrate │
│ → DB_HOST from CF outputs │
│ → DB_PASSWORD from SSM SecureString │
│ → V1__init.sql, V2__add_users.sql, V3__add_orders.sql │
└─────────────────────────────────────────────────────────────────┘
  • How ${env.*} runtime resolution works in flyway.toml — values injected by the GitLab pipeline, not at build time
  • How chant build --lexicon <name> selects only the resources belonging to that lexicon from a mixed src/ directory
  • The SSM SecureString pattern: password never appears in source code or CF templates

~$50/mo while running: RDS db.t3.micro ~$15/mo, NAT gateway ~$32/mo, data transfer ~$3/mo. Teardown after testing.

Set up the flyway-postgresql-gitlab-aws-rds example for GitLab CI deployment.

See examples/flyway-postgresql-gitlab-aws-rds/ for the full README, prerequisites, and step-by-step deploy instructions.

Each file in src/ declares resources for one lexicon, but they all live in the same directory:

src/
├── network.ts # AWS — VpcDefault composite
├── database.ts # AWS — RdsInstance composite
├── params.ts # AWS — CloudFormation parameters
├── outputs.ts # AWS — CloudFormation outputs
├── tags.ts # AWS — default tags
├── migrations.ts # Flyway — FlywayProject, FlywayConfig, Environment
└── pipeline.ts # GitLab — 2-stage pipeline

chant build --lexicon aws -o templates/template.json discovers only the AWS declarables. chant build --lexicon flyway -o flyway.toml discovers only the Flyway declarables. The lexicon filter runs at discovery time — no manual grouping needed.

Runtime variable resolution with $&#123;env.*&#125;

Section titled “Runtime variable resolution with $&#123;env.*&#125;”

FlywayProject uses ${env.DB_HOST} and ${env.DB_PASSWORD} — these appear literally in the generated flyway.toml. Flyway resolves them at migration time from environment variables that the GitLab pipeline sets:

src/migrations.ts
export const deployEnv = new Environment({
name: "deploy",
url: `jdbc:postgresql://\${env.DB_HOST}:5432/appdb`,
user: "appuser",
password: "${env.DB_PASSWORD}",
});

The pipeline extracts DB_HOST from CF stack outputs and DB_PASSWORD from SSM, then runs flyway migrate. The password is fetched at runtime — it never appears in flyway.toml, source code, or CI logs.

Terminal window
# One-time setup: store password in SSM (KMS-encrypted)
./setup.sh /myapp/dev/db-password
# Pipeline fetches it at runtime
DB_PASSWORD=$(aws ssm get-parameter --name /myapp/dev/db-password --with-decryption --query 'Parameter.Value' --output text)

The CloudFormation template receives the SSM path as a parameter (DbPasswordSsmPath), not the password itself. RDS uses the SSM parameter to set the master password via ManageMasterUserPassword: false + direct reference — the password never travels through CF.