run9 forks the whole box environment, so a database fits naturally into the same model. If the database files live in the box and Hooks start and stop the daemon at the right moments, every fork becomes a new database branch without any database-specific product mode.
This is a natural fit when one PostgreSQL baseline should split into several realistic lines of work:
migration-afor one schema change rehearsalmigration-bfor a competing migration plancustomer-incidentfor replaying one production issue without touching the other branches
What you are building
docker.io/library/ubuntu:24.04
|
v
box: pg-base
(PostgreSQL + hooks + seed data)
|
v
snap: <forked-snap-id>
/ | \
v v v
migration-a migration-b customer-incident
The database is not special here. It is just one more part of the environment that the fork carries forward.
PostgreSQL: prepare one parent box
Create the box and open one interactive shell:
run9 box create pg-base --image docker.io/library/ubuntu:24.04
run9 box exec pg-base -it bash
Inside that shell:
# inside the shell:
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql
cat > /etc/run9_on_start.sh <<'EOF'
#!/bin/sh
set -eu
pg_ctlcluster 16 main start
EOF
cat > /etc/run9_on_stop.sh <<'EOF'
#!/bin/sh
set -eu
pg_ctlcluster 16 main stop --mode fast
EOF
chmod +x /etc/run9_on_start.sh /etc/run9_on_stop.sh
# start PostgreSQL once for the current shell session
pg_ctlcluster 16 main start
su - postgres
createdb app
psql app
Inside the psql prompt:
create table notes(branch text, body text);
insert into notes values ('base', 'seed row from the parent box');
select * from notes order by branch, body;
\q
Then leave the PostgreSQL user shell and the box shell:
exit
exit
Those hook files become part of the box state, so later forks inherit both the database files and the wake/stop behavior. The manual pg_ctlcluster ... start above is only for this first live session.
Fork the whole environment
Stop the parent first so on_stop can flush the database and the fork captures a quiet file system:
run9 box stop pg-base
run9 snap fork --from-box pg-base
run9 box create migration-a --snap <forked-snap-id>
run9 box create migration-b --snap <forked-snap-id>
run9 box create customer-incident --snap <forked-snap-id>
At this point:
- all three boxes start from the same PostgreSQL data directory
- all three branches inherited the same hook scripts
- waking any branch starts PostgreSQL automatically
That is the whole idea behind database forking in run9: fork the environment, and the database comes with it.
Rehearse migration A
run9 box exec migration-a -it bash
Inside the shell:
# inside the shell:
su - postgres
psql app
Inside the psql prompt:
insert into notes values ('migration-a', 'only on migration a');
select * from notes order by branch, body;
\q
Exit the shells:
exit
exit
Rehearse migration B
run9 box exec migration-b -it bash
Inside the shell:
# inside the shell:
su - postgres
psql app
Inside the psql prompt:
insert into notes values ('migration-b', 'only on migration b');
select * from notes order by branch, body;
\q
Exit the shells:
exit
exit
Replay one customer incident
run9 box exec customer-incident -it bash
Inside the shell:
# inside the shell:
su - postgres
psql app
Inside the psql prompt:
insert into notes values ('customer-incident', 'only on the customer incident branch');
select * from notes order by branch, body;
\q
Exit the shells:
exit
exit
What the branches now contain
migration-a shows:
branch | body
--------+-----------------------------
base | seed row from the parent box
migration-a | only on migration a
migration-b shows:
branch | body
--------+-----------------------------
base | seed row from the parent box
migration-b | only on migration b
customer-incident shows:
branch | body
--------+------------------------------------------
base | seed row from the parent box
customer-incident | only on the customer incident branch
If you later want a new branch that already includes changes from migration-a, stop migration-a and run run9 snap fork --from-box migration-a. Fork from the box state you want future branches to inherit.
MongoDB follows the same pattern
MongoDB uses the same environment-fork pattern:
- prepare one parent box
- put
mongodstart and stop logic in/etc/run9_on_start.shand/etc/run9_on_stop.sh - insert seed data
- stop the box
snap fork --from-box ...- create branch boxes from the forked snap
Example setup:
run9 box create mongo-base --image mongodb/mongodb-community-server:7.0-ubuntu2204
run9 box exec mongo-base -it bash
Inside that shell:
# inside the shell:
mkdir -p /data/db /var/log/mongodb
cat > /etc/run9_on_start.sh <<'EOF'
#!/bin/sh
set -eu
mongod --dbpath /data/db --bind_ip 127.0.0.1 --logpath /var/log/mongodb/mongod.log --fork
EOF
cat > /etc/run9_on_stop.sh <<'EOF'
#!/bin/sh
set -eu
mongosh --quiet --eval 'db.getSiblingDB("admin").shutdownServer()'
EOF
chmod +x /etc/run9_on_start.sh /etc/run9_on_stop.sh
mongod --dbpath /data/db --bind_ip 127.0.0.1 --logpath /var/log/mongodb/mongod.log --fork
mongosh
Inside mongosh:
db.notes.insertOne({ branch: "base", body: "seed row from the parent box" })
db.notes.find().toArray()
exit
Then exit the shell, stop the box, fork the snap, and create branch boxes exactly as in the PostgreSQL example above.