Endgame Viable
GoToSocial Migration By Prompt
This serves as an example of how to use agentic AI to assist with all those projects you’ve been putting off. I captured my conversation history to show how it works, in case anyone out there in a development role still isn’t doing this every day.
The goal here was to migrate my private GoToSocial instance and a Postgresql database from my local network Unraid server to a VPS host that I’ve been renting for years but never used much.
I wrote the prompts, Gemini wrote the plans and scripts, and I ran the commands. This is Gemini 3.5 Flash (Medium) in Antigravity IDE.
I didn’t include every Gemini answer because that would have turned this into a novel. Imagine there was an exhaustive technical answer to every prompt.
Part 1: Feasibility
User Prompts
I want to make a plan to migrate a gotosocial instance and postgres server installed on my local Unraid server to an Internet virtual server of some kind. Can you give me some options? Is it even possible?
1 - I would like to use a low-cost, self-managed VPS, I may already have one actually 2 - it uses local storage I think, 3 - the script connected to the postgres database directly
Will I be able to migrate the existing gts.endgameviable.com domain to the new server so that federation continues to work seamlessly
I have a VPS plan at Vultr with these specs, will that be enough to run a low-traffic gotosocial and postgres containers? AMD High Performance
1 vCPU, 1024 MB RAM, 25 GB NVMe, 2.00 TB Transfer
Gemini’s Plan
GoToSocial & Postgres VPS Migration Plan
This plan details the migration of your GoToSocial instance (gts.endgameviable.com) from your local Unraid server to a self-managed, low-cost Linux VPS.
🏗️ Target Architecture (VPS)
For a self-managed VPS (e.g., Hetzner, DigitalOcean, Linode), we will run a clean Docker Compose stack that includes PostgreSQL, GoToSocial, and Caddy (which automatically handles Let’s Encrypt SSL certificates).
1. docker-compose.yml (On VPS)
Create a directory (e.g., ~/gotosocial) on the VPS and define the services:
services:
postgres:
image: postgres:15-alpine
container_name: gts-postgres
restart: always
environment:
POSTGRES_DB: gotosocial
POSTGRES_USER: gotosocial
POSTGRES_PASSWORD: "YOUR_SECURE_PASSWORD" # Match or update from Unraid
volumes:
- ./postgres_data:/var/lib/postgresql/data
# Note: We do NOT expose port 5432 to the public internet for security.
gotosocial:
image: superseriousbusiness/gotosocial:latest
container_name: gotosocial
restart: always
environment:
GTS_HOST: gts.endgameviable.com
GTS_DB_TYPE: postgres
GTS_DB_ADDRESS: postgres
GTS_DB_PORT: 5432
GTS_DB_USER: gotosocial
GTS_DB_PASSWORD: "YOUR_SECURE_PASSWORD"
GTS_DB_DATABASE: gotosocial
GTS_STORAGE_BACKEND: local
GTS_STORAGE_LOCAL_DIR: /gotosocial/storage
GTS_ACCOUNTS_REGISTRATION_OPEN: "false"
GTS_PORT: 8080
volumes:
- ./gts_storage:/gotosocial/storage
depends_on:
- postgres
caddy:
image: caddy:2-alpine
container_name: caddy-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- gotosocial
volumes:
caddy_data:
caddy_config:
2. Caddyfile (On VPS)
Caddy will serve as the reverse proxy and handle SSL handshakes automatically:
gts.endgameviable.com {
reverse_proxy gotosocial:8080
}
🔄 Updating the Ingestion Script (gts-archiver)
Since your python scripts currently use psql to query tower.local directly, moving Postgres to the VPS will break this connection. Here are two ways to solve this:
Option A: Query via SSH Tunnel (Minimum Change)
To keep using SQL queries without exposing Postgres port 5432 to the public internet, you can create a secure SSH tunnel right before running the query.
- Establish a tunnel in your terminal:
ssh -N -L 5432:localhost:5432 user@YOUR_VPS_IP - Update the python scripts:
Change the
psql_commandhost target fromtower.localtolocalhost:psql_command = [ "psql", "-h", "localhost", # Changed from tower.local "-d", "gotosocial", "-U", "gotosocial", "-c", sql, "-o", "output.csv", "--csv" ]
Option B: Rewrite Script to use the GoToSocial API (Recommended & Cleaner)
GoToSocial provides a Mastodon-compatible API. Rewriting the script to fetch posts via HTTP makes the database completely private, removes the need for SSH tunnels, removes hardcoded passwords, and allows you to automate the script (e.g. via GitHub Actions or AWS CodeBuild) from anywhere.
Here is how the query portion of fetchposts.py can be refactored using Python’s standard libraries:
import urllib.request
import json
# Your GoToSocial account ID
account_id = "redacted"
api_url = f"https://gts.endgameviable.com/api/v1/accounts/{account_id}/statuses?limit=40"
# Fetch statuses from the public API
req = urllib.request.Request(api_url, headers={'User-Agent': 'GTS-Archiver'})
try:
with urllib.request.urlopen(req) as response:
statuses = json.loads(response.read().decode())
except Exception as e:
print(f"Error fetching from API: {e}")
sys.exit(1)
# Filter and format statuses in-memory (replacing the SQL conditions)
result_rows = []
for status in statuses:
created_at_dt = datetime.strptime(status["created_at"], "%Y-%m-%dT%H:%M:%S.%fZ")
# Apply date filters (start/end)
if not (yesterday <= created_at_dt < today):
continue
# Apply content filters
if "https://gts.endgameviable.com/tags/notification" in status["content"]:
continue
# Map API response fields to match the rest of your script
result_rows.append({
"id": status["id"],
"in_reply_to_id": status["in_reply_to_id"],
"url": status["url"],
"visibility": status["visibility"],
"content_warning": status["spoiler_text"],
"content": status["content"],
"created_at": status["created_at"],
"dayname": created_at_dt.strftime("%A"),
"day": created_at_dt.strftime("%m/%d"),
"hour": created_at_dt.strftime("%H:%M")
})
🗺️ Step-by-Step Migration Blueprint
Follow these steps to perform the migration:
Phase 1: Prepare the VPS
- Provision VPS: Deploy an Ubuntu instance and install Docker.
- Configure Security Rules: Allow incoming traffic on ports
80,443(for web/SSL), and22(SSH). - Write Configuration Files: Set up the
docker-compose.ymlandCaddyfileon the VPS as shown above. Do not rundocker compose upyet.
Phase 2: Lower DNS TTL (24 hours prior)
- Go to AWS Route 53.
- Select the hosted zone for
endgameviable.com. - Locate the A record for
gts.endgameviable.comand set the TTL to300seconds (5 minutes) so the IP switch takes effect quickly.
Phase 3: Backup and Export (On Unraid)
- Stop GoToSocial: Shut down the GoToSocial container in Unraid to prevent new database entries or files during migration.
- Dump PostgreSQL Database:
Run
pg_dumpon your Unraid Postgres container:docker exec -t [postgres-container-name] pg_dump -U gotosocial -F c -d gotosocial > gotosocial_backup.dump - Archive Storage Directory:
Compress your GoToSocial local media/appdata storage:
tar -czvf gotosocial_storage.tar.gz -C /mnt/user/appdata/gotosocial/ storage/
Phase 4: Transfer and Restore (On VPS)
- Transfer Files: Securely copy the backup files to your new VPS:
scp gotosocial_backup.dump gotosocial_storage.tar.gz user@your-vps-ip:~/gotosocial/ - Restore Media Storage:
Extract the storage archive on the VPS:
tar -xzvf ~/gotosocial/gotosocial_storage.tar.gz -C ~/gotosocial/ # This creates ~/gotosocial/storage/ which maps to `./gts_storage` mv ~/gotosocial/storage ~/gotosocial/gts_storage - Restore Database:
- Start only the Postgres container on the VPS:
docker compose up -d postgres - Restore the database dump:
docker exec -i gts-postgres pg_restore -U gotosocial -C -d gotosocial < ~/gotosocial/gotosocial_backup.dump
- Start only the Postgres container on the VPS:
Phase 5: Point DNS and Start
- Update Route 53: Update the A record for
gts.endgameviable.comto point to the VPS’s public IP address. - Start All Services:
docker compose up -d - Verify:
- Run
docker compose logs caddyand verify it successfully obtained SSL certificates forgts.endgameviable.com. - Open
https://gts.endgameviable.comin your browser, log in, and make a test post. - Search for your test post from an external Mastodon account to ensure bidirectional federation is functioning.
- Run
- Restore TTL: Change the Route 53 A record TTL back to
3600(1 hour). - Update local script: Apply either Option A (SSH tunnel) or Option B (API-based query) to your local
gts-archiverscripts.
Part 2: Local Test
User Prompts
I've created a new directory gts-orchestration and would like to use that directory to setup a local test instance of the gotosocial stack
Is there a way to export Unraid docker container vars or do I have to type them by hand from the UI?
Here are my existing gotosocial vars, update the plan to use the same vars
oot@Tower:~# docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' gotosocial
GTS_ACCOUNTS_REGISTRATION_OPEN=false
GTS_STATUSES_MAX_CHARS=2000
GTS_LETSENCRYPT_EMAIL_ADDRESS=[REDACTED_EMAIL]
GTS_DB_TYPE=postgres
HOST_HOSTNAME=Tower
GTS_HOST=gts.endgameviable.com
GTS_DB_USER=gotosocial
GTS_DB_PASSWORD=[REDACTED_PASSWORD]
GTS_LANDING_PAGE_USER=ultrviolet
GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE=true
HOST_OS=Unraid
HOST_CONTAINERNAME=gotosocial
GTS_DB_ADDRESS=[REDACTED_IP]
GTS_LETSENCRYPT_ENABLED=false
GTS_LOG_LEVEL=debug
TZ=America/New_York
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Update the plan to configure env vars for docker compose for postgres password and GTS_HOST
Update the plan so the postgres password is only in the .env file
Update the plan to remove "ultrviolet" from docker-compose.yml and .env.sample
Can you update the plan to add Makefile targets to add and remove gts.endgameviable.com to /etc/hosts - is that possible?
I have docker desktop installed but I get this
tkrehbiel@MacBook-Pro gts-orchestration % make up
docker compose up -d
make: docker: No such file or directory
make: *** [up] Error 1
It seems like it's already installed... it doesn't let me apply
Now I get this
tkrehbiel@MacBook-Pro gts-orchestration % make up
/usr/local/bin/docker compose up -d
[+] up 0/3
⠋ Image postgres:15-alpine Pulling 0.0s
⠋ Image superseriousbusiness/gotosocial:latest Pulling 0.0s
⠋ Image caddy:2-alpine Pulling 0.0s
error getting credentials - err: exec: "docker-credential-desktop": executable file not found in $PATH, out: ``
make: *** [up] Error 1
It started up, now what?
Can I open anything in a browser without setting up gts.endagmeviable.com?
For now I don't want to restore a database, I just want to see if it runs before I work on migrating data
Okay it works, now I want to start making a plan to migrate data from the Unraid postgres database. How do I export data from postgres?
I've never ssh'ed to the unraid server, is it just "ssh tower.local"?
tkrehbiel@MacBook-Pro gts-orchestration % ssh root@tower.local
ssh: connect to host tower.local port 22: Connection refused
I don't see appdata in the list of shares.. it's got a warning icon next to it in the unraid UI
The postgres backup only has 54 bytes, I don't think it worked
pg_dump: error: invalid output format "-c" specified
I've imported the backup files into the project dir, make a plan to import them into my local test setup
I got these warnings
root@Tower:~# docker exec -i postgresql15 pg_dump -U gotosocial -F c -d gotosocial > /mnt/user/LocalBackups/gotosocial_backup.dumpWARNING: database "gotosocial" has a collation version mismatch
DETAIL: The database was created using collation version 2.36, but the operating system provides version 2.41.
HINT: Rebuild all objects in this database that use the default collation and run ALTER DATABASE gotosocial REFRESH COLLATION VERSION, or build PostgreSQL with the right library version.
I updated the dump file, continue the plan
The site said this, how do I see logs
An error occured:
Internal Server Error
Request ID: 000037ya5a58p0000010
I see this warning at the top of the gts page
Warning! It looks like either trusted-proxies is not set correctly in this instance's configuration, or the upstream reverse proxy is not passing an appropriate X-Forwarded-For or X-Real-IP header.
This may cause rate-limiting issues and, by extension, federation issues.
If you are the instance admin, you may be able to fix this by adding [REDACTED_IP]/16 to your trusted-proxies.
For more information and other possible fixes for this issue, see the documentation.
So that was an issue in the local environment only, and wouldn't have happened in a live environment?
Gemini’s Plan
Implementation Plan - Import Unraid Backups into Local Test Setup
This plan details the steps to import your Unraid PostgreSQL dump (gotosocial_backup.dump) and GoToSocial media storage (gotosocial_storage.tar.gz) into the local docker-compose environment inside gts-orchestration.
Because the restored database contains internal ActivityPub links bound to gts.endgameviable.com, we must revert the local configuration from localhost back to gts.endgameviable.com and use the local Caddy reverse proxy with hosts-file mapping.
Proposed Configuration Changes
1. Update .env
We must change GTS_HOST back to the production domain so that GoToSocial matches the restored database records:
POSTGRES_PASSWORD=[REDACTED_PASSWORD]
GTS_HOST=gts.endgameviable.com
GTS_STATUSES_MAX_CHARS=2000
GTS_LANDING_PAGE_USER=ultrviolet
GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE=true
GTS_LOG_LEVEL=debug
TZ=America/New_York
Import Execution Steps
Follow these steps in your terminal to clear the dummy local data and import your Unraid production data:
Step 1: Wipe Clean and Restart the Stack
We must blow away the temporary local database and media storage to avoid any conflicts:
# Stop containers and delete local docker volumes
make clean
Now, start the stack back up. This will pull in the updated .env configuration and initialize a fresh, empty PostgreSQL database:
make up
Step 2: Extract Media Storage
Extract the compressed media files and place them in the local folder mapped by docker-compose (gts_storage):
# Extract the tarball (creates a "storage" directory)
tar -xzvf gotosocial_storage.tar.gz
# Rename the folder to match the docker volume mount path
mv storage gts_storage
Step 3: Restore the PostgreSQL Database
Restore the custom-format database dump using the make restore-db helper:
make restore-db DUMP=gotosocial_backup.dump
[!NOTE] Since we ran
make cleanfirst, this will restore to a clean database without conflicts.
Step 4: Configure Local DNS
Route requests for gts.endgameviable.com to your local machine:
make hosts-add
Verification Plan
- Open your browser and navigate to:
https://gts.endgameviable.com - Proceed past the browser’s local HTTPS/self-signed certificate warning.
- Verify that your posts, profiles, and media attachments load correctly (indicating that the database and files were successfully restored).
- Run
make logsto verify that GoToSocial is not printing any database connection or filesystem access errors.
Part 3: VPS Deployment & Verification
User Prompts
Now that I've tested locally, how can I install this setup on my Vultr VPS server?
Make a plan, don't change things
Vult instance os is Ubuntu 22.04 x64
I currently have gts.endgameviable.com pointed to a CloudFront distribution... should I continue to use the CloudFront distribution and point it to the Vultr server? Or is that a waste?
Is there any risk of breaking federation during the time period when I switch gts.endgameviable.com but haven't yet started the gotosocial server?
If a server is declared dead in federation, and it comes back months later, will federation resume or is it dead forever?
How do I verify that the domain name is pointing to the Vultr server if there's no web server on the VPS server
tkrehbiel@MacBook-Pro gts-orchestration % tar -czvf gts-deploy.tar.gz docker-compose.prod.yml Caddyfile.prod .e
nv gotosocial_backup.dump gotosocial_storage.tar.gz
tar: docker-compose.prod.yml: Cannot stat: No such file or directory
tar: Caddyfile.prod: Cannot stat: No such file or directory
a .env
a gotosocial_backup.dump
a gotosocial_storage.tar.gz
tar: Error exit delayed from previous errors.
What next?
I've just updated the OS on the VPS to ubuntu 24.04
As part of the plan, create a shell script to run all the necessary ubuntu setup from a fresh os install, and make it idempotent
The setup script should also setup starting docker compose on rebooting
Add a make target to package files for transfer
I packaged the files and scp'ed them to the VPS, now what?
Is this an issue?
root@development:~# tar -xzf /tmp/gts-deploy.tar.gz -C ~/gts-orchestration
tar: Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.com.apple.provenance'
tar: Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.com.apple.provenance'
tar: Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.com.apple.provenance'
tar: Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.com.apple.provenance'
tar: Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.com.apple.provenance'
tar: Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.com.apple.provenance'
I got a ton of errors restoring the pg backup
root@development:~/gts-orchestration# docker compose exec -T postgres pg_restore -U gotosocial -d gotosocial --clean < gotosocial_backup.dump
pg_restore: error: could not execute query: ERROR: index "web_push_subscriptions_account_id_idx" does not exist
(snip)
Command was: DROP TABLE public.account_notes;
pg_restore: warning: errors ignored on restore: 256
I don't see a page loading at gts.endgameviable.com yet
root@development:~/gts-orchestration# docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
caddy caddy:2-alpine "caddy run --config …" caddy About a minute ago Up About a minute 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp, 443/udp, 2019/tcp
gotosocial superseriousbusiness/gotosocial:latest "/gotosocial/gotosoc…" gotosocial About a minute ago Up About a minute
gts-postgres postgres:15-alpine "docker-entrypoint.s…" postgres About a minute ago Up About a minute 5432/tcp
root@development:~/gts-orchestration# docker compose logs caddy
caddy | {"level":"info","ts":1785877892.8641872,"msg":"maxprocs: Leaving GOMAXPROCS=1: CPU quota undefined"}
(snip)
caddy | {"level":"info","ts":1785877898.657089,"logger":"tls.obtain","msg":"releasing lock","identifier":"gts.endgameviable.com"}
The gotosocial log is enormous but I don't see any errors
root@development:~/gts-orchestration# curl -Iv https://gts.endgameviable.com
* Host gts.endgameviable.com:443 was resolved.
(snip)
<
* Connection #0 to host gts.endgameviable.com left intact
https://gts.endgameviable.com/@ultrviolet seems to work though
Chrome is saying my connection to gts.endgamevaible.com is not secure
(Those connectivity problems turned out to be a problem with Chrome caching. The server itself worked on the first try.)
Gemini’s Plan
Deploy GotoSocial to Vultr VPS (Ubuntu 24.04 x64)
Outline the steps, configuration changes, and verification required to migrate the local GotoSocial, Postgres, and Caddy orchestration setup to a remote Vultr virtual private server (VPS) running Ubuntu 24.04 x64.
User Review Required
[!IMPORTANT] Postgres and Port Security: In the local development
docker-compose.yml, the PostgreSQL port5432and GotoSocial port8080are exposed to the host machine. In production, we must disable these mappings so they are only accessible through the internal Docker bridge network and not reachable from the public internet. Caddy will be the sole public gateway on ports80and443.
[!WARNING] SSL Certificate Transition: The local setup uses
tls internalin the Caddyfile. For production, this directive must be removed so Caddy can automatically request and manage Let’s Encrypt or ZeroSSL certificates forgts.endgameviable.com.
Open Questions
[!IMPORTANT]
- Domain DNS: Has
gts.endgameviable.combeen pointed to your Vultr VPS’s public IP address yet? This is required before Caddy can successfully provision its SSL certificate.- Docker Status: Are Docker and Docker Compose already installed on your VPS, or do they need to be installed as part of this process? (Installation steps for Ubuntu 22.04 are provided below if needed).
Proposed Changes
Docker Orchestration Setup
[MODIFY] Makefile
- Add target
pack-deployto package production settings, DB dump, storage archive, and setup script into a singlegts-deploy.tar.gzarchive.
[NEW] docker-compose.prod.yml
- Security hardening: Remove exposed ports for
postgres(5432) andgotosocial(8080). - Use production-appropriate container names (
gts-postgres,gotosocial,caddy) instead of-localsuffixes. - Adjust
GTS_LOG_LEVELtowarnto avoid disk space bloat.
[NEW] Caddyfile.prod
- Remove
tls internalto enable Let’s Encrypt / ZeroSSL certificate management automatically.
[NEW] setup_vps.sh
- Idempotent Bash script to automate UFW firewall, package updates, Docker installation, folder creation, and enabling Docker and Containerd on boot (supporting the compose container restart policies on system reboot).
Step-by-Step Deployment Guide
Here is the plan for how you will run the installation on your VPS:
Step 1: Package and Transfer Local Setup
- On your local machine, run the following command in the orchestration directory to pack the deployment files:
This generates
tar -czvf gts-deploy.tar.gz docker-compose.prod.yml Caddyfile.prod setup_vps.sh .env gotosocial_backup.dump gotosocial_storage.tar.gzgts-deploy.tar.gzcontaining the production settings and setup script. - Transfer this archive to your Vultr VPS (replace
USERandVPS_IPwith your actual VPS ssh details):scp gts-deploy.tar.gz USER@VPS_IP:/tmp/
Step 2: Set up and Restore on the VPS
- SSH into your VPS:
ssh USER@VPS_IP - Create the orchestration directory and extract the files:
mkdir -p ~/gts-orchestration tar -xzf /tmp/gts-deploy.tar.gz -C ~/gts-orchestration cd ~/gts-orchestration - Run the idempotent setup script to prepare the OS, firewall, Docker, and directories:
chmod +x setup_vps.sh sudo ./setup_vps.sh - Prepare configuration filenames for docker compose:
mv docker-compose.prod.yml docker-compose.yml mv Caddyfile.prod Caddyfile - Extract the existing storage backup into the
gts_storagedirectory:tar -xzf gotosocial_storage.tar.gz -C gts_storage/ --strip-components=1 - Start the PostgreSQL service first to restore the database:
docker compose up -d postgres - Restore the PostgreSQL database dump:
# Wait a few seconds for postgres to be ready, then: docker compose exec -T postgres pg_restore -U gotosocial -d gotosocial --clean < gotosocial_backup.dump - Start the rest of the services (GotoSocial and Caddy):
docker compose up -d
Verification Plan
Automated Tests
- Check status of all containers:
docker compose ps - Check logs for Caddy to ensure SSL certificates were obtained:
docker compose logs caddy - Check logs for GotoSocial to ensure database connection and start:
docker compose logs gotosocial
Manual Verification
- Open
https://gts.endgameviable.comin a browser and verify that the instance is loading with a valid Let’s Encrypt / ZeroSSL HTTPS certificate. - Verify that your user accounts and posts are fully restored and media images display correctly.
And that was it. Easy peasy.
P.S. I painstakingly cut and paste those prompts and plans into a document, and only then did I realize I could just ask Gemini to read its conversation history and create the document for me in seconds. Live and learn.
Leave a Comment
This is a homegrown DIY comment system I've been working on. It works but technically you're beta testing it. Good luck. Go here to enter a comment on this post without Javascript.