I have a CDK Pipelines
pipeline that is handling the self mutation and deployment of my application on ECS and I am having a tough time figuring out how to implement database migrations.
My migration files as well as the migration command reside inside of the docker container that are built and deployed in the pipeline. Below are two things I've tried so far:
My first thought was just creating a pre
step on the stage, but i believe there is a chicken/egg situation. Since the migration command requires database to exist (as well as having the endpoint and credentials) and the migration step is pre
, the stack doesn't exist when this command would run...
const pipeline = new CodePipeline(this, "CdkCodePipeline", {
// ...
// ...
}
pipeline.addStage(applicationStage).addPre(new CodeBuildStep("MigrateDatabase", {
input: pipeline.cloudAssemblyFileSet,
buildEnvironment: {
environmentVariables: {
DB_HOST: { value: databaseProxyEndpoint },
// ...
// ...
},
privileged: true,
buildImage: LinuxBuildImage.fromAsset(this, 'Image', {
directory: path.join(__dirname, '../../docker/php'),
}),
},
commands: [
'cd /var/www/html',
'php artisan migrate --force',
],
}))
In the above code, databaseProxyEndpoint
has been everything from a CfnOutput, SSM Parameter to a plain old typescript reference but all failed due to the value being empty, missing, or not generated yet.
I felt this was close, since it works perfectly fine until I try and reference databaseProxyEndpoint
.
My second attempt was to create an init container in ECS.
const migrationContainer = webApplicationLoadBalancer.taskDefinition.addContainer('init', {
image: ecs.ContainerImage.fromDockerImageAsset(webPhpDockerImageAsset),
essential: false,
logging: logger,
environment: {
DB_HOST: databaseProxy.endpoint,
// ...
// ...
},
secrets: {
DB_PASSWORD: ecs.Secret.fromSecretsManager(databaseSecret, 'password')
},
command: [
"sh",
"-c",
[
"php artisan migrate --force",
].join(" && "),
]
});
// Make sure migrations run and our init container return success
serviceContainer.addContainerDependencies({
container: migrationContainer,
condition: ecs.ContainerDependencyCondition.SUCCESS,
});
This worked, but I am not a fan at all. The migration command should run once in the ci/cd pipeline on a deploy, not when the ECS service starts/restarts or scales... My migrations failed once and it locked up cloudformation because the health check failed both on the deploy and then naturally on the rollback as well causing a completely broken loop of pain.
Any ideas or suggestions on how to pull this off would save me from losing the remaining hair i have left!