Seeding your database with data
Applications often rely on a static dataset to pre-exist in the database in order to function. Examples for such datasets include user roles, country names, or product categories.
The process of populating the database with this initial data is called "seeding". This guide will show you how to seed your database using Atlas.
Versioned Workflow
migrate new
If you are using versioned migrations to manage your schema, seeding can be done by introducing a new migration file
that contains INSERT
statements with the desired data.
To create the empty migration file, run:
atlas migrate new
Next, paste your desired INSERT
statements.
For example, for a schema containing a table called users
with name
and age
columns,
INSERT INTO users (name, age)
VALUES
('John', 30),
('Jane', 32);
After saving the changes to the migration file, run:
atlas migrate hash
This command will recalculate the checksums for the migration directory in atlas.sum
.
Finally, run atlas migrate apply
and Atlas will automatically apply the INSERT
statements to your database.
Template Directories
Another option when working with a versioned workflow is using dynamic template-based directories.
For example, let's say you have a simple table users
that you want to add two records to.
enum "roles" {
schema = schema.public
values = ["user", "admin"]
}
table "users" {
schema = schema.public
column "role" {
type = enum.roles
}
column "data" {
type = json
null = true
}
}
schema "public"{}
First, define the data you plan to insert as a JSON Lines file:
{"name": "Ariel"}
{"name": "Rotem"}
Next, update your atlas.hcl
file to parse the data and pass it to the migration template:
locals {
# The path is relative to the `atlas.hcl` file.
seed_users = split("\n", file("seed_users.json"))
}
data "template_dir" "migrations" {
path = "migrations"
vars = {
seed_users = local.seed_users
}
}
env "dev" {
dev = "docker://postgres/17/dev"
migration {
dir = data.template_dir.migrations.url
}
}
Finally, create a new migration file by running atlas migrate new seed_users --edit
and paste the following statement:
{{ range $line := .seed_users }}
INSERT INTO `users` (`role`, `data`) VALUES ('user', '{{ $line }}');
{{ end }}
This file expects a data variable named seed_users
of type []string
. It then loops over this variable and INSERT
s
a record into the users table for each JSON line.
To check that our data interpolation works as expected, run atlas migrate apply
on a temporary PostgreSQL container
that Atlas will spin up and tear down automatically for us:
atlas migrate apply \
--env dev \
--url docker://postgres/17/dev
Example output
Migrating to version 20230719102332 (2 migrations in total):
-- migrating version 20230719093802
-> CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role` enum('user', 'admin') NOT NULL,
`data` json,
PRIMARY KEY (`id`)
) AUTO_INCREMENT=1000;
-- ok (38.380244ms)
-- migrating version 20230719102332
-> INSERT INTO `users` (`role`, `data`) VALUES ('user', '{"name": "Ariel"}');
-> INSERT INTO `users` (`role`, `data`) VALUES ('user', '{"name": "Rotem"}');
-- ok (13.313962ms)
-------------------------
-- 95.387439ms
-- 2 migrations
-- 3 sql statements
Declarative Workflow
Currently, there is no support through Atlas for defining a "data state". However, it is possible to use the
schema plan
command and edit the plan to include INSERT
statements.
The atlas schema plan
command is available exclusively to Pro users. To use this feature, run:
atlas login
Local Example
Setup
Let's start with a local example. We'll begin with a base schema.pg.hcl
file containing a table users
and configure an atlas.hcl
file with our local database URL, dev database URL, and schema file path:
- Schema Definition
- Config File
table "users" {
schema = schema.public
column "data" {
type = json
null = true
}
}
schema "public" {}
env "local" {
# URL to the target database
url = "postgres://postgres:pass@localhost:5432/dev?sslmode=disable"
# URL to the dev-database
dev = "docker://postgres/17/dev"
# Desired schema state
schema {
src = "file://schema.pg.hcl"
}
}
Set up your local database with this schema by running
atlas schema apply --env local --auto-approve
Changing the Schema
Let's add a role
column to the users
table and an enum
type to represent it.
enum "roles" {
schema = schema.public
values = ["user", "admin"]
}
table "users" {
schema = schema.public
column "role" {
type = enum.roles
}
column "data" {
type = json
null = true
}
}
schema "public" {}
To locally generate a migration plan for adding the role
column to the users
table and save the plan to
a file that can be edited manually, run:
atlas schema plan --save --env local
The output will look something like this:
Planning migration from local database to file://schema.pg.hcl (2 statements in total):
-- create enum type "role":
-> CREATE TYPE "public"."role" AS ENUM ('user', 'admin');
-- modify "users" table:
-> ALTER TABLE "public"."users" ADD COLUMN "role" "public"."role" NOT NULL;
-------------------------------------------
Analyzing planned statements (2 in total):
-- data dependent changes detected:
-- L4: Adding a non-nullable "role" column "role" will fail in case table "users" is
not empty https://atlasgo.io/lint/analyzers#MF103
-- ok (287.375µs)
-------------------------
-- 50.243375ms
-- 2 schema changes
-- 1 diagnostic
-------------------------------------------
Plan Status: SAVED
-- Atlas URL: file://20250317112745.plan.hcl
Open the file and add the INSERT
statements for the data you want to input to your database to the plan's migration
field.
plan "20250317112745" {
from = "E+w50mxU/6XEiChiDmS+uGi154Q5QXy4l+F3eawDaT0="
to = "STrYroQXyw6QSYDu8Y8yHC9qUtH5CSmLVuJ7imhqgZM="
migration = <<-SQL
-- Create enum type "role"
CREATE TYPE "public"."role" AS ENUM ('user', 'admin');
-- Modify "users" table
ALTER TABLE "public"."users" ADD COLUMN "role" "public"."role" NOT NULL;
INSERT INTO users (role, data) VALUES
('user', '{"name": "Ariel"}'),
('user', '{"name": "Rotem"}');
SQL
}
Finally, running atlas schema apply --env local --plan "file://20250317112745.plan.hcl"
will apply the changes to the database.
Applying approved migration using pre-planned file 20250317112745 (3 statements in total):
-- create enum type "role"
-> CREATE TYPE "public"."role" AS ENUM ('user', 'admin');
-- ok (703.625µs)
-- modify "users" table
-> ALTER TABLE "public"."users" ADD COLUMN "role" "public"."role" NOT NULL;
-- ok (692.75µs)
-- applying statement 3
-> INSERT INTO users (role, data) VALUES
('user', '{"name": "Ariel"}'),
('user', '{"name": "Rotem"}');
-- ok (307.458µs)
-------------------------
-- 1.825875ms
-- 1 migration
-- 3 sql statements
Using Atlas Registry
Teams using Declarative Migrations with the Schema Registry, typically have plans generated during the PR (Pull Request) process and stored in the repository.
These plans are presented as a GitHub (or equivalent source control management platform) comment:
The bottom of the comment contains instructions for editing the plan file and applying the changes, these plans look similar to this:
📝 Steps to edit this migration plan
1. Run the following command to pull the generated plan to your local workstation:
atlas schema plan pull --url "atlas://raggler/plans/pr-11187-EOZSqdJl" > pr-11187-EOZSqdJl.plan.hcl
2. Open pr-11187-EOZSqdJl
in your editor and modify it as needed. Note that the result of the plan should align
the database with the desired state. Otherwise, Atlas will report a schema drift.
3. Push the updated plan to the registry using the following command:
atlas schema plan push --pending --file pr-11187-EOZSqdJl.plan.hcl
4. Re-trigger the schema/plan
action to update this comment:
gh run rerun 13964259192
To incorporate data seeding into such a plan, simply add DML (INSERT
/UPDATE
/DELETE
) statements to the
migration
field of the plan file before applying it running the schema push
command and re-triggering the
schema/plan
action.
Have additional questions or feedback? Feel free to reach out on our Discord server.