[homepage|forum|cv] WM-101 [text|html] [remarks]
              
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. 🕸️