Skip to main content

Pre & Post Execution Hooks

Pre-deployment scripts let you prepare the environment, such as taking a snapshot, before running atlas migrate apply, while post-deployment scripts help you validate, seed or update data, and clean up after the migration completes.

Atlas supports both through pre and post execution hooks, allowing you to run SQL statements or external scripts at each stage to coordinate complex rollouts. Common use cases include running pre-deployment scripts that enforce guardrails, applying post-deployment scripts that seed lookup tables, or triggering post-deployment scripts that refresh reporting views.

Pre and post execution hooks are available to Atlas Pro users. You can create a trial account using the atlas login command. To learn more about logged-in features, see Features page.

Overview

Pre and post execution hooks are configured inside the relevant environment in your atlas.hcl config file. Each hook targets the migrate_apply command and runs outside the migration transaction:

atlas.hcl
env "dev" {
pre "migrate_apply" {
exec {
sql = <<-SQL
CALL ensure_guardrails();
CALL validate_data_integrity();
SQL
}
}

post "migrate_apply" {
exec {
sql = <<-SQL
REFRESH MATERIALIZED VIEW CONCURRENTLY analytics.latest_activity;
CALL sync_reporting_cache();
SQL
}
}
}

Atlas executes all pre "migrate_apply" hooks sequentially before applying any migration file. If all pre hooks succeed, the pending migrations are executed. Once the migration is complete, the post "migrate_apply" hooks run in order.

Transaction Context

To configure custom logic for the transaction or connection lifecycle, such as setting statement_timeout or lock_timeout, refer to transaction hooks.

Skipping Hooks

You can skip a hook programmatically by adding a skip block. When condition evaluates to true the hook is skipped and the optional message is printed to the CLI output:

atlas.hcl
env {
name = atlas.env
pre "migrate_apply" {
skip {
condition = atlas.env == "staging"
message = "Skipping staging warm-up"
}
exec {
src = "file://scripts/pre-flight.sql"
}
}
}

Executing SQL or Scripts

Hooks execute statements via an exec block. There are two ways to supply the payload:

  • sql: Inline SQL statement(s) to run directly.
  • src: Path or URL to a file containing statements. Atlas supports any source registered with the file:// URL scheme.
atlas.hcl
variable "script_file" {
type = string
}

env "dev" {
pre "migrate_apply" {
exec {
src = var.script_file
}
}
}

Provide the variable at runtime, for example:

atlas migrate apply \
--dir "file://migrations" \
--url "sqlite://file?mode=memory" \
--config "file://atlas.hcl" \
--env "dev" \
--var "script_file=file://scripts/pre.sql"
Example output
Executing pre-migration hooks (1 hook in total):

-- hook 1/1 at atlas.hcl:11 (2 statements):
-> CALL ensure_guardrails();
-> CALL validate_data_integrity();
-- ok (4.583µs)

-------------------------------------------

Migrating to version 1 (1 migrations in total):

-- migrating version 1
-> CREATE TABLE t1(c1 int);
-> ALTER TABLE t1 ADD COLUMN c2 int;
-- ok (148.583µs)

-------------------------
-- 911.5µs
-- 1 migration
-- 2 sql statements

-------------------------------------------

Executing post-migration hooks (1 hook in total):

-- hook 1/1 at atlas.hcl:23 (2 statements):
-> REFRESH MATERIALIZED VIEW CONCURRENTLY analytics.latest_activity;
-> CALL sync_reporting_cache();
-- ok (4.375µs)

Error Handling

Use the on_error attribute to control what happens when a hook statement fails. The supported values are:

  • FAIL (default, pre-hooks only): stops executing the current hook and aborts the migration run.
  • BREAK: stops executing the current hook, skips the remaining hooks, and continues the migration.
  • CONTINUE: skips the failing hook and proceeds to the next one.
  • SKIP_FILE: aborts the remaining statements in the current file and resumes execution at the next file. The hook itself is still considered successful.
atlas.hcl
env "dev" {
pre "migrate_apply" {
exec {
src = "file://scripts/pre-flight.sql"
on_error = CONTINUE
}
}
}

Skipping Failed Files

SKIP_FILE is useful when src points to a directory of scripts and you want a failure in one file to skip only the rest of that file, rather than aborting the run (FAIL), the remaining hooks (BREAK), or the rest of the hook (CONTINUE). When a statement fails, the remaining statements in the same file are skipped and execution resumes at the next file in the directory:

atlas.hcl
env "dev" {
post "migrate_apply" {
exec {
src = "file://scripts"
on_error = SKIP_FILE
}
}
}

When src expands to one or more named files, the apply output renders per-file headers so skipped files are clearly visible:

  -- hook 1/1 at atlas.hcl:7 (4 statements):
-- file 1.sql (1 statement):
-> INSERT INTO t1 (c1) VALUES (10);
-- file 2.sql (2 statements):
-> INSERT INTO no_such_table (c1) VALUES (99);
no such table: no_such_table
-- skipped
-- file 3.sql (1 statement):
-> INSERT INTO t1 (c1) VALUES (30);
-- ok

SKIP_FILE is only valid with src. Using it with inline sql is rejected at config-parse time, since there is no file to skip; use CONTINUE or BREAK instead.

Seeding Reference Data

Post-deployment scripts are a great way to keep lookup tables (for example, configuration tables with static values) aligned across environments. In PostgreSQL, keep these statements in version-controlled SQL files and execute them through hooks:

atlas.hcl
env "prod" {
post "migrate_apply" {
exec {
src = "file://scripts/config_table.sql"
on_error = BREAK
}
}
}

The referenced script can use MERGE to upsert the canonical seed values while keeping the table tidy:

scripts/config_table.sql
MERGE INTO config_table AS target
USING (VALUES
('max_login_attempts', '5', 'Maximum allowed failed login attempts'),
('session_timeout_minutes', '30', 'User session timeout duration'),
('password_min_length', '12', 'Minimum password length requirement'),
) AS source(key, value, description)
ON target.key = source.key
WHEN MATCHED THEN
UPDATE SET value = source.value, description = source.description
WHEN NOT MATCHED THEN
INSERT (key, value, description) VALUES (source.key, source.value, source.description);

Each deployment keeps schema and reference data synchronized without relying on manual follow-up steps.