Before you start
You need:- Your running v4 instance, and a recent backup of its PostgreSQL database.
- Access to the v4 database (the source) and to a new, empty PostgreSQL database for the new installation (the target).
- A plan for the new installation. Self-hosting with Docker is the usual choice.
Choose a method
Import resumes one by one
The current version reads v4 JSON exports directly and converts them.Export from v4
Import into the new installation
Check and repeat
Run the migration scripts
The scripts live in the repository at tagv5.0.20, the last release that includes them. They were written against the database schema of that release, so you create the target database with that release first, run the scripts, and only then start the current version, which brings the data forward with its own migrations.
Prepare the scripts
Installtsx and a .env loader such as dotenvx on the machine that runs the scripts, then check out the tag:
.env file at the root of the checkout:
Create the target schema
Apply thev5.0.20 migrations to the empty target database:
Migrate users
- Reads v4 users in batches and creates matching users in the target.
- Copies sign-in methods (email and password, Google, GitHub, custom OAuth). Two-factor authentication isn’t copied: every migrated user starts with it turned off.
- Writes
scripts/migration/user-id-map.json, which links each v4 user ID to its new ID. The resume script needs it. - Saves progress to
scripts/migration/user-progress.jsonafter each batch. If the run stops, run it again to continue from the last saved batch.
Migrate resumes
- Reads v4 resumes in batches, converts each to the new format, and assigns it to the right user through the ID map.
- Copies view and download statistics, public or private visibility, and the locked state.
- Saves progress to
scripts/migration/resume-progress.jsonafter each batch, and continues from there if you run it again.
SIGINT. Prefer letting a batch finish before
stopping it. After an interruption, retain the progress and ID-map files and verify the last batch before resuming.Start the current version
Point a current Reactive Resume installation at the target database and start it. On startup it applies every migration sincev5.0.20, which also fills in the new columns for the migrated users. Watch the logs for Database migrations completed.
After the migration
Check the data
Test the essentials
Switch traffic
Retire v4
What carries over
Passwords
Passwords
Profile pictures and images
Profile pictures and images
Two-factor authentication
Two-factor authentication
Schema differences
Schema differences
visibility became a public/private flag, a resume’s title became its name, and resume data was
reorganized. The scripts and the importer handle these conversions.Recover one person’s resumes later
Sometimes a person changes a resume in v4 after the main migration, or a resume didn’t come across. Handle this as a recovery case rather than re-running the scripts, and never overwrite the resume in the new installation. Keep a private record for each case, outside Git, because it links account and resume identifiers:- Case ID and the capture time of the source snapshot.
- How you verified the person owns the account.
- Source resume ID and mapped target resume ID, if there is one.
- Source and target content hashes, and the outcome:
no-op,export-copy, orblocked.
Confirm a source exists
Verify ownership and mapping
Compare without writing
no-op. Content that exists only in the source, or differs, is an export-copy. Missing
evidence, mapping, valid data, or source is blocked. Content hashes prove equality only, never ownership or
authenticity.Deliver a separate private export
tooling/recovery/compare-resume.ts. It takes one serialized JSON request whose source and target already match the current resume schema, produces a dry-run manifest, and never touches a database or the network. It doesn’t convert raw v4 data; review the converter at tag v5.0.20 for that. Use synthetic data only.
Troubleshooting
The script says PRODUCTION_DATABASE_URL is not set
The script says PRODUCTION_DATABASE_URL is not set
DATABASE_URL and PRODUCTION_DATABASE_URL in .env and run the scripts through dotenvx run -- (or
export the variables yourself).Users are skipped
Users are skipped
Resumes are skipped
Resumes are skipped
A resume comes across empty
A resume comes across empty
Migrated users can't sign in
Migrated users can't sign in
Database migrations completed.The migration is slow
The migration is slow