Server
Run Gitupcms on a Linux VM with systemd and Caddy. This is how app.gitupcms.com runs.
app.gitupcms.com runs on an AWS Lightsail instance (4 GB, Ubuntu) with Caddy in front for HTTPS. Any Linux VM works the same way.
Install Node, pnpm and Caddy
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt-get install -y nodejs build-essential git rsync
sudo npm i -g pnpm@10
# Caddy: see caddyserver.com/docs/installCreate a service user and folders
sudo useradd --system --home /var/lib/gitupcms --shell /usr/sbin/nologin gitupcms
sudo mkdir -p /opt/gitupcms /var/lib/gitupcms/data /var/lib/gitupcms/previews
sudo chown -R gitupcms:gitupcms /opt/gitupcms /var/lib/gitupcmsWrite the environment
BETTER_AUTH_URL=https://cms.example.com
BETTER_AUTH_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
DB_PATH=/var/lib/gitupcms/data/gitupcms.db
HOST=127.0.0.1
PORT=4321
PREVIEW_RUNNER=1
PREVIEW_DIR=/var/lib/gitupcms/previews
PREVIEW_DOCKER=node:24-bookworm-slimMake it readable only by root and the service: chown root:gitupcms, chmod 640.
PREVIEW_DOCKER runs each site’s install and astro dev in a container. Install Docker, let the service use it and keep containers off the cloud metadata address:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker gitupcms
sudo docker pull node:24-bookworm-slim
sudo iptables -I DOCKER-USER -d 169.254.169.254 -j DROP # persist with iptables-persistentDocker group membership is root-equivalent for the service user; leave PREVIEW_DOCKER unset if you don’t run previews.
Copy and build
rsync -az --exclude node_modules --exclude dist --exclude .env --exclude data ./cms/ server:/opt/gitupcms/
sudo -u gitupcms sh -c 'cd /opt/gitupcms && pnpm install --frozen-lockfile && pnpm build'Run it with systemd
[Unit]
Description=Gitupcms
After=network.target
[Service]
User=gitupcms
Group=gitupcms
WorkingDirectory=/opt/gitupcms
EnvironmentFile=/etc/gitupcms.env
Environment=NODE_ENV=production
ExecStart=/usr/bin/node ./dist/server/entry.mjs
Restart=always
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/gitupcms /opt/gitupcms /tmp
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now gitupcmsHTTPS with Caddy
cms.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:4321 {
flush_interval -1
}
}flush_interval -1 keeps the AI Editor’s streamed answers flowing. Point a DNS A record at the server and reload Caddy; it gets the certificate itself.
Live preview (optional)
With PREVIEW_HOST=https://*.preview.cms.example.com, the AI Editor runs each page’s scripts on its own subdomain. Add a wildcard A record (*.preview.cms.example.com) and a wildcard certificate, which needs a DNS challenge: a Caddy build with your DNS provider (caddy-dns), e.g. Route 53:
*.preview.cms.example.com {
tls {
dns route53
}
reverse_proxy 127.0.0.1:4322 {
flush_interval -1
}
}Give Caddy the DNS credentials through a systemd EnvironmentFile, and drop --environ from its ExecStart, or it writes them to the log.
Updating
Copy the new source, then build and restart:
sudo -u gitupcms sh -c 'cd /opt/gitupcms && pnpm install --frozen-lockfile && pnpm build'
sudo systemctl restart gitupcms
Logs and backups
- Logs:
journalctl -u gitupcms -f. WithoutSMTP_URL, email sign-in codes appear here. - Back up the SQLite file in
DB_PATH(for example withsqlite3 gitupcms.db ".backup backup.db"). Content is in git; the database holds users, workspaces, settings, media metadata, comments and feedback.