budgit/docs/first-time-deployment.md
2026-02-09 21:13:46 +00:00

5 KiB

First-Time Deployment

This guide walks through every manual step needed on a fresh server before the CI/CD workflow can auto-deploy. After completing this once, all future deploys happen automatically when you push a v* tag.

Prerequisites

  • A Linux server (Debian/Ubuntu assumed, adjust package commands for other distros)
  • Root or sudo access
  • A domain name pointed at the server's IP
  • PostgreSQL 15+ installed
  • Caddy installed

Step 1: Create the system user

Create a dedicated budgit user for deployment:

sudo useradd --create-home --shell /bin/bash budgit

Grant the deploy user the specific sudo permissions it needs (no password):

sudo tee /etc/sudoers.d/budgit> /dev/null << 'EOF'
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart budgit, /usr/bin/systemctl status budgit
EOF
sudo chmod 440 /etc/sudoers.d/budgit

Step 2: Create the application directory

sudo mkdir -p /opt/budgit
sudo chown budgit:budgit /opt/budgit
sudo chmod 750 /opt/budgit

Step 3: Set up PostgreSQL

Follow docs/database-setup.md in full. By the end you should have:

  • A budgit-admin PostgreSQL role
  • A budgit database owned by budgit-admin
  • pg_hba.conf peer auth with an ident map so the budgit system user authenticates as budgit-admin

Verify it works:

sudo -u budgit psql -U budgit-admin -d budgit -c "SELECT 1;"

Step 4: Create the environment file

sudo -u budgit tee /opt/budgit/.env > /dev/null << 'EOF'
APP_ENV=production
APP_URL=https://budgit.now
HOST=127.0.0.1
PORT=9000

DB_DRIVER=pgx
DB_CONNECTION=postgres://budgit-admin@/budgit?host=/run/postgresql&sslmode=disable

JWT_SECRET=<run: openssl rand -base64 32>

MAILER_SMTP_HOST=
MAILER_SMTP_PORT=587
MAILER_IMAP_HOST=
MAILER_IMAP_PORT=993
MAILER_USERNAME=
MAILER_PASSWORD=
MAILER_EMAIL_FROM=
SUPPORT_EMAIL=
EOF

Generate and fill in the JWT_SECRET:

openssl rand -base64 32

Fill in the mailer variables if email is configured. Lock down permissions:

sudo chmod 600 /opt/budgit/.env

Step 5: Do the initial binary deploy

Build locally (or on any machine with Go + Tailwind + Task installed):

task build

Copy the binary to the server:

scp ./dist/budgit your-user@your-server:/tmp/budgit
ssh your-user@your-server "sudo mv /tmp/budgit /opt/budgit/budgit && sudo chown budgit:budgit /opt/budgit/budgit && sudo chmod 755 /opt/budgit/budgit"

Step 6: Install the systemd service

Copy the unit file from this repo:

scp docs/budgit.service your-user@your-server:/tmp/budgit.service
ssh your-user@your-server "sudo mv /tmp/budgit.service /etc/systemd/system/budgit.service"

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable budgit
sudo systemctl start budgit

Check it's running:

sudo systemctl status budgit
curl http://127.0.0.1:9000/healthz

You should see ok.

Step 7: Configure Caddy

Replace the existing budgit.now site block in your Caddyfile (typically /etc/caddy/Caddyfile).

Before (static file server):

budgit.now, www.budgit.now, mta-sts.budgit.now, autodiscover.budgit.now {
    import common_headers
    import budgit_now_ssl
    root * /var/www/budgit.now
    file_server
}

After (split app from other subdomains):

budgit.now, www.budgit.now {
    import common_headers
    import budgit_now_ssl
    encode gzip zstd

    handle /.well-known/* {
        root * /var/www/budgit.now
        file_server
    }

    handle {
        reverse_proxy 127.0.0.1:9000 {
            health_uri /healthz
            health_interval 10s
            health_timeout 3s
        }
    }
}

mta-sts.budgit.now, autodiscover.budgit.now {
    import common_headers
    import budgit_now_ssl
    root * /var/www/budgit.now
    file_server
}

Reload Caddy:

sudo systemctl reload caddy

Verify the public endpoint:

curl https://budgit.now/healthz

Step 8: Configure Forgejo secrets

In your Forgejo repository, go to Settings > Secrets and add:

Secret Value
SSH_KEY Contents of deploy_key (the private key)
SSH_USER deploy
SSH_HOST Your server's IP or hostname
DEPLOY_PATH /opt/budgit
APP_URL https://budgit.now

Step 9: Verify auto-deploy

Tag and push to trigger the workflow:

git tag v0.1.0
git push origin v0.1.0

Watch the workflow in Forgejo's Actions tab. It should:

  1. Build the binary with the version baked in
  2. SCP it to the server
  3. Restart the service
  4. Pass the health check

Confirm the version is running:

journalctl -u budgit --no-pager -n 5

You should see a log line like server starting version=v0.1.0.

Summary

After completing these steps, the deployment flow is:

git tag v1.2.3 && git push origin v1.2.3
  -> Forgejo workflow triggers
  -> Builds binary with version embedded
  -> SCPs to server, restarts systemd
  -> Health check verifies
  -> Auto-rollback on failure

No further manual steps are needed for subsequent deploys.