Skip to content

Reconfigure Domain

deployment/reconfigure-domain.sh re-runs the domain configuration on an existing install without re-installing the stack. Use it when the initial deployment/server-setup.sh ran without a domain (NO_TUNNEL=1 HOST=<ip>), or when you change the public domain after the fact. The script rewrites every host-bearing env var in .env, rebuilds apps/web (because NEXT_PUBLIC_* are baked at build time), and restarts doable.service.

The script is idempotent. Running it twice with the same DOMAIN is a no-op after the first pass.

When to use it

  • The original install was NO_TUNNEL=1 HOST=<ip>, so .env carries CORS_ORIGINS=https://<ip>, NEXT_PUBLIC_*=https://<ip>, and so on, and you now want the install to publish at a real domain.
  • You moved from one domain to another, or from staging to production.
  • You added a Cloudflare ACM cert and want to flip from the prefix layout (<env>-slug.zone) to the infix layout (slug.<env>.zone).

Prerequisites

  • An existing Doable install on the server, typically at /root/doable (override with --install-dir <path> or INSTALL_DIR=).
  • Write permission on <INSTALL_DIR>/.env. The script errors out if the file is missing or read-only.
  • The DOMAIN you pass must already have DNS pointed at the Cloudflare Tunnel (otherwise the smoke test at the end will report 530s but the env rewrite still succeeds).

Usage

# Env-style
DOMAIN=acme.example.com ./deployment/reconfigure-domain.sh

# Flag-style
./deployment/reconfigure-domain.sh --domain dev.doable.me

# Multi-level zones work too
./deployment/reconfigure-domain.sh --domain staging.doable.me

Cloudflare-friendly subdomain layout

The script derives the API and WS hostnames from DOMAIN using Doable's Cloudflare-naming rule, which exists because free Universal SSL covers <zone> and *.<zone> only (one-level wildcard).

DOMAIN API hostname WS hostname Layout
acme.example.com (apex) api.acme.example.com ws.acme.example.com Dotted
dev.doable.me (multi-level) dev-api.doable.me dev-ws.doable.me Dashed

Override per call if needed:

./deployment/reconfigure-domain.sh \
  --domain dev.doable.me \
  --api-domain api-dev.doable.me \
  --ws-domain ws-dev.doable.me

Publish layout: prefix vs infix

Published apps default to a single-level wildcard (prefix layout): <prefix><slug>.<zone>. With Cloudflare ACM you can opt into the infix layout (<slug>.<env>.<zone>):

# Prefix (default, free Universal SSL): https://dev-myapp-abc.doable.me
./deployment/reconfigure-domain.sh --domain dev.doable.me --layout prefix

# Infix (requires Cloudflare ACM): https://myapp-abc.dev.doable.me
./deployment/reconfigure-domain.sh --domain dev.doable.me \
  --layout infix --wildcard-hostname '*.dev.doable.me'

Tip: if PUBLISH_LAYOUT already exists in .env, the script keeps that value unless --layout overrides it.

Flag reference

Flag Default Notes
--domain <zone> $DOMAIN env Required if env not set.
--install-dir <path> /root/doable or $INSTALL_DIR Where .env lives.
--api-domain <host> Derived from DOMAIN Override the computed API host.
--ws-domain <host> Derived from DOMAIN Override the computed WS host.
--layout prefix\|infix Existing or prefix Sets PUBLISH_LAYOUT.
--wildcard-hostname '*.zone' *.<domain> (infix only) Must start with *. and live inside the zone.
--publish-prefix <s> Existing or derived Overrides PUBLISH_SUBDOMAIN_PREFIX.
--no-rebuild false Skip pnpm --filter @doable/web build. The running bundle still has the OLD NEXT_PUBLIC_* baked in until you rebuild.
--no-restart false Skip systemctl restart doable.service.
--dry-run false Print the diff but write nothing.

What it rewrites

Every host-bearing env var in <INSTALL_DIR>/.env is rewritten in place. A timestamped backup is created at <INSTALL_DIR>/.env.pre-domain-YYYYMMDD-HHMMSS.

Variables touched:

NEXT_PUBLIC_APP_URL    https://<domain>
NEXT_PUBLIC_API_URL    https://<api-domain>
NEXT_PUBLIC_WS_URL     wss://<ws-domain>
CORS_ORIGINS           https://<domain>
WS_ALLOWED_ORIGINS     https://<domain>
DOABLE_DOMAIN          <publish-apex>
PUBLISH_LAYOUT         prefix | infix
PUBLISH_SUBDOMAIN_PREFIX <prefix>
WILDCARD_HOSTNAME      *.<wildcard-bare>
GOOGLE_REDIRECT_URI    https://<api-domain>/auth/google/callback
GITHUB_REDIRECT_URI    https://<api-domain>/oauth/github/login/callback
GITHUB_COPILOT_REDIRECT_URI https://<api-domain>/oauth/github/copilot/callback
GITHUB_REPO_REDIRECT_URI    https://<api-domain>/oauth/github/repo/callback
INTEGRATIONS_OAUTH_REDIRECT_URI https://<api-domain>/integrations/oauth/callback
INTEGRATIONS_ENHANCED_AUTH_REDIRECT_URI https://<api-domain>/integrations/enhanced-auth/callback

The script also writes apps/web/.env.local with the new NEXT_PUBLIC_* values to close a Next.js .env precedence trap: a stale .env.local would otherwise silently outvote the rewrites at build time.

Build + restart

By default the script:

  1. Backs up .env to .env.pre-domain-YYYYMMDD-HHMMSS.
  2. Rewrites every key above in place.
  3. Syncs apps/web/.env.local.
  4. Runs env -u NODE_ENV NODE_ENV=production pnpm --filter @doable/web build as the install owner (3 to 6 minutes).
  5. Runs systemctl restart doable.service.
  6. Smoke-tests http://127.0.0.1:{3000,4000,4001} plus the public URLs.

Skip steps 4 and 5 with --no-rebuild and --no-restart respectively, but remember that the running web bundle still has the OLD URLs baked in until you rebuild.

Smoke test output

After it runs, the script prints something like:

api (127.0.0.1:4000/health): 200
web (127.0.0.1:3000/):       200
ws  (127.0.0.1:4001/health): 200

Public hostnames (DNS + TLS must be live for these to pass):
  https://dev.doable.me/         -> 200
  https://dev-api.doable.me/health -> 200
  https://dev-ws.doable.me/health  -> 200

If the public URLs return 530, the Cloudflare Tunnel routes are missing. Add them:

cloudflared tunnel route dns --overwrite-dns <tunnel-uuid> dev.doable.me
cloudflared tunnel route dns --overwrite-dns <tunnel-uuid> dev-api.doable.me
cloudflared tunnel route dns --overwrite-dns <tunnel-uuid> dev-ws.doable.me

Diffing the rewrite

diff -u /root/doable/.env.pre-domain-YYYYMMDD-HHMMSS /root/doable/.env

Troubleshooting

DOMAIN is required. Set DOMAIN= in the environment or pass --domain <zone>.

.env not found. Confirm --install-dir points at a real Doable install. By default the script looks at /root/doable.

.env is not writable. Run as the install owner (typically root) or chown to the right user.

Web still serves the old URLs. Either the build step was skipped (--no-rebuild) or systemctl restart doable.service did not pick up the new bundle. Run the script again without those flags.

Reference