Master Kubernetes database migrations using Liquibase and InitContainers. Learn how to automate schema versioning for reliable, zero-downtime CI/CD deployments.
I’ve seen too many production incidents caused by a mismatch between application code and the database schema. When you're running at scale, manual SQL scripts are a liability. If you're still running psql or mysql commands from your local machine to update production, stop. It’s time to bake your migrations into your deployment pipeline.
In this guide, I’ll show you how to handle Kubernetes database migrations using Liquibase and InitContainers. This pattern ensures your schema is always in the state the application expects before the pod even starts accepting traffic.
In a distributed system, you can’t assume the database is ready or migrated when your app container boots. By using an InitContainer, we force a synchronous dependency: the application container won't start until the migration job finishes successfully.
If the migration fails, the pod enters a CrashLoopBackOff, preventing a broken application version from reaching your users. It’s a simple, effective fail-safe.
Liquibase is my go-to tool for schema versioning because it tracks changes in a DATABASECHANGELOG table. It’s declarative, supports rollbacks, and integrates perfectly into DevOps database automation workflows.
First, create your changelog file (e.g., db/changelog.xml) and package it into a Docker image. I prefer a "migration-only" image that contains the Liquibase binary and your SQL files.
Dockerfile# Dockerfile for migrations FROM liquibase/liquibase:4.24.0 COPY changelog /liquibase/changelog COPY lib /liquibase/lib ENTRYPOINT ["/liquibase/liquibase"]
Now, update your Deployment manifest. We’ll use an initContainers block to run the migration before the main application container starts.
YAMLapiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: spec: initContainers: - name: db-migrate image: my-registry/my-app-migrations:v1.2.0 command: ["/liquibase/liquibase"] args: - "--url=jdbc:postgresql://postgres-service:5432/mydb" - "--username=$(DB_USER)" - "--password=$(DB_PASSWORD)" - "--changeLogFile=changelog/master.xml" - "update" envFrom: - secretRef: name: db-credentials containers: - name: app image: my-registry/my-app:v1.2.0 # ... rest of your config
For effective Liquibase CI/CD, your pipeline should handle the image build and push. Don’t run migrations from your local laptop.
Because Kubernetes is declarative, it will detect the change in the initContainer image, trigger a rolling update, and run the migrations on the new pods.
Liquibase uses a DATABASECHANGELOGLOCK table to prevent multiple instances from migrating simultaneously. This is critical in Kubernetes. If you're doing a rolling update where multiple pods start at once, the first pod will acquire the lock, and the others will wait.
If a migration fails mid-way, the lock might stay active. You can clear it manually if needed, but in an automated environment, I suggest adding a liquibase release-locks command as a fallback if you ever run into race conditions during high-concurrency deployments.
Init:CrashLoopBackOff. This is a clear indicator that your migration failed.DB_USER and DB_PASSWORD into the initContainer environment.Automating your database lifecycle is a hallmark of a mature engineering team. By moving your Kubernetes database migrations into InitContainers, you gain consistency, predictability, and a safety net that prevents half-baked releases.
It takes a bit of work to set up the migration images, but once it's in your CI/CD pipeline, you’ll never worry about schema drift again.
Got questions on how to handle complex migrations or rollbacks? Drop a comment below.
Master Argo Rollouts for automated canary deployments. Learn how to implement Kubernetes GitOps and traffic shifting to improve your software delivery pipeline.
Read moreMaster GitOps with Argo CD for robust Kubernetes CD. Learn how to implement declarative infrastructure and automate your deployments with this step-by-step guide.