Document: WM-101 P. Webb
Category: Tutorial 2026-09-02
How to migrate from Gel to Disc, in Production
Abstract
Unforeseen gotchas galore, but I figured it out.
Body
The last post/guide about this[1] only handled local development and
as we know, what "works on my machine" often doesn’t translate well
to other machines.
In order to perform the backup of my Gel database on my server, I
first needed to create the gel-export folder in the directory of
the app Gel was running in before giving ownership and permissions to
the gel user.
# run these commands as root user
chown gel: gel-export
chmod u+w gel-export
su gel
su gel switches you from the root user to the gel user.
Replace INSTANCE with the name of your Gel instance to get your
user and password (if you don’t already know them).
# run this command as gel user
gel instance credentials --insecure-dsn -I INSTANCE
# you should see something like
gel:///DATABASE?port=PORT&user=USER&password=PASSWORD&tls_ca_file=%3C...%3E&tls_security=no_host_verification
# run this command as gel user
# you will be prompted for that password you saw earlier
gel dump --all --format=dir gel-export --admin --user USER --instance INSTANCE --tls-security insecure --password
I ran into errors complaining about missing packages so I exited back
to my root user and ran these:
apt install -y postgresql-common
/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
apt install -y postgresql-client-18
I was able to proceed with the rest of the export process after that.
When deploying my new Disc-aware program though, I found I needed two
systemd files. The server install script[2] for Disc is designed to
"just work" and will create a service file for you and have Disc
running automatically but there’s another running process that’s
required, and that’s after creating a new user (as root) and giving
said user a password. I named my new user disco because disc was
already taken by the install script and you cannot switch to said
user, for security reasons (no login allowed).
Here is my service file for the Nickel API:
[Unit]
Description=NICKEL API
Documentation=https://nickel.video
After=network.target
[Service]
ExecStartPre=/opt/disc/bin/disc start
ExecStartPre=/opt/disc/bin/disc migrate
ExecStartPre=/opt/disc/bin/disc codegen
ExecStart=/var/www/api/nickel
Group=disco
Restart=on-failure
User=disco
WorkingDirectory=/var/www/api
[Install]
WantedBy=multi-user.target
The Nickel API is a compiled executable. Before that starts running,
I have Disc boot up, run migrations if necessary, and codegen for the
compiled SDK. It’s quite possible this can be optimized.
Oh, I had to ensure the disco user had ownership over the codegen
directory (dbschema/) so migrations wouldn’t fail:
chown -R disco:disco /var/www/api/dbschema
Here is the other service file I had to make to serve the Disc UI and
database for the API to reach. I should probably rename it to
api-serve so it’s not confusing to future me.
[Unit]
Description=Disc Start Server
After=network.target postgresql.service api.nickel.service
Wants=disc.service api.nickel.service
[Service]
ExecStart=/opt/disc/bin/disc serve
Group=disco
Restart=on-failure
RestartSec=5
SyslogIdentifier=disc-start
User=disco
WorkingDirectory=/var/www/api
[Install]
WantedBy=multi-user.target
Once confiming the service files worked, I had to finish the data
import process.
su disco
disc migrate
disc db import ./gel-export/data
disc db import ./gel-export/data --on-conflict skip
Computed fields in my Gel export gave me issues. I made backup copies
of the affected files, deleted the columns causing problems, and the
import went through! Fully prepared to manually enter the data of the
deleted columns, I accessed the Disc UI and was surprised to see my
computed columns present! Which, makes sense in hindsight…the other
data in the backup would obviously compute these fields on import.
I’ll add a fox for that in Disc.
This was quite a painful process TBQH. I was obviously motiviated to
push through it but good grief! The "Happy Path" and DX needs tuning.
The Disc UI doesn’t come with authentication so that you can design
your own (or just disable it in production). Here’s how I handle that
with Caddy:
# generate a password in your favorite password manager and
# have Caddy hash it
caddy hash-password
Make note of that generated hash! Then, update basic_auth in your
Caddyfile (and make sure PORT matches whatever your Disc UI wants:
database.nickel.video {
basic_auth {
SOME_USERNAME <SOME_HASH>
}
encode gzip
reverse_proxy localhost:PORT
root * /path/to/your/project
}
Last step is to configure your firewall!
ufw allow https
ufw allow ssh
ufw deny PORT
ufw enable
This makes your server reachable via HTTPS (no plain HTTP), allows
you to continue connecting to your server via SSH, rejects any
outside connections to your Disc UI’s port, and enables the firewall.
I feel like this "tutorial" was all over the place and I certainly
felt that way while I was trying to keep track of all the pieces,
haha! At some point I had to uninstall Gel because I think it was
interfering with Disc.
Please excuse the mess, there’s more work to be done to make Disc the
best, smoothest database to ever exist. 🕸️