Crikket

Caddy

Deploy Crikket behind the built-in Caddy reverse proxy with automatic HTTPS.

Use this guide when you want the repo's install wizard to configure Crikket for a VPS with:

  • Caddy handling TLS and reverse proxying
  • one frontend domain, such as app.example.com
  • one API domain, such as api.example.com
  • Docker Compose running the stack

This guide follows the actual prompts from ./scripts/setup.sh.

What This Deployment Uses

  • docker-compose.yml for the main stack
  • docker-compose.caddy.yml for the bundled Caddy container
  • Caddyfile in the repo root for the bundled Caddy config
  • ./scripts/setup.sh for the install wizard
  • .env, apps/server/.env, and apps/web/.env generated or rewritten by the wizard

Before You Start

  • A Linux VPS with Docker Engine and Docker Compose v2 installed
  • A public IPv4 address for the VPS
  • Two DNS names pointing at that server:
    • app domain, for example app.example.com
    • API domain, for example api.example.com
  • Ports 80 and 443 open to the internet
  • S3-compatible object storage credentials

Recommended baseline:

  • Ubuntu 22.04+
  • 2 vCPU
  • 4 GB RAM
  • 40 GB SSD

DNS Layout

Create both DNS records before you start the stack. Do not rely on creating them after Caddy is already trying to issue certificates.

Example:

app.example.com  ->  your VPS public IP
api.example.com  ->  your VPS public IP

If your DNS provider supports proxying, start with plain DNS-only records until Caddy has issued certificates successfully.

Wait for public DNS propagation before you answer yes to the wizard's final "Start Crikket now with Docker Compose" prompt.

Recommended checks:

dig @1.1.1.1 +short app.example.com
dig @1.1.1.1 +short api.example.com

Both commands should return your VPS public IP.

Connect to the VPS and Clone the Repo

git clone https://github.com/redpangilinan/crikket
cd crikket

Run everything else from the repository root.

Run the Install Wizard

./scripts/setup.sh

The wizard validates the repo layout, reads any existing env files as defaults, and creates timestamped backups before rewriting them.

If older env files already exist, the wizard prints that it will use the current values as defaults and back them up before rewrite.

Answer the Domain Prompts

The wizard asks for these first:

Frontend domain
Backend/API domain

Example answers:

Frontend domain: app.example.com
Backend/API domain: api.example.com

Rules enforced by the wizard:

  • frontend and backend domains must be different
  • the values must be hostnames, not full URLs
  • Caddy mode requires real public domains, not localhost or raw IPs

From those values, the wizard auto-builds:

  • NEXT_PUBLIC_APP_URL=https://app.example.com
  • NEXT_PUBLIC_SITE_URL=https://app.example.com
  • NEXT_PUBLIC_SERVER_URL=https://api.example.com
  • BETTER_AUTH_URL=https://api.example.com
  • CORS_ORIGINS=https://app.example.com

It also tries to derive BETTER_AUTH_COOKIE_DOMAIN from the shared suffix.

Enable Caddy in the Wizard

The next important prompt is:

Enable built-in Caddy reverse proxy with automatic HTTPS [Y/n]

Answer yes.

The wizard then asks for:

Email for Caddy TLS certificates

Use a real email address. Caddy uses it for ACME certificate management.

In Caddy mode, the wizard automatically keeps the internal app bindings private by default:

  • WEB_PORT=127.0.0.1:3001
  • SERVER_PORT=127.0.0.1:3000

That means only Caddy needs to be public on 80 and 443.

Choose the Database Mode

The wizard asks:

Use an external PostgreSQL database [y/N]

Choose one:

  • no: the repo uses the bundled Postgres service from docker-compose.yml
  • yes: the wizard switches to docker-compose.external-db.yml and asks for Database URL

If you are just getting started on one VPS, bundled Postgres is the simplest path.

Finish the Required App Questions

The wizard always asks for storage settings because uploads need S3-compatible storage:

Storage bucket name
Storage access key ID
Storage secret access key
Storage endpoint URL (leave blank for AWS S3)
Storage region
Storage public URL (optional)

It also asks whether you want to configure these optional integrations now:

  • Google OAuth sign-in
  • Resend email delivery
  • Upstash Redis for capture rate limiting
  • Cloudflare Turnstile for capture bot protection
  • PostHog client analytics

If you do not have those ready yet, answer no and add them later.

Review the Files the Wizard Writes

When the prompts are complete, the wizard writes:

  • .env
  • apps/server/.env
  • apps/web/.env

The repo-owned Caddyfile reads its values from those env files. Caddy mode settings include:

CRIKKET_PROXY_MODE=caddy
CADDY_HTTP_PORT=80
CADDY_HTTPS_PORT=443
CADDY_ACME_EMAIL=you@example.com
NEXT_PUBLIC_APP_URL=https://app.example.com
NEXT_PUBLIC_SERVER_URL=https://api.example.com

The committed Caddyfile looks like this shape:

{
	email {$CADDY_ACME_EMAIL}
}

{$NEXT_PUBLIC_APP_URL} {
	encode gzip zstd
	reverse_proxy server:3001
}

{$NEXT_PUBLIC_SERVER_URL} {
	encode gzip zstd
	reverse_proxy server:3000
}

In this stack, web shares the server network namespace, so Caddy proxies both frontend and API traffic to the server service on different ports.

Start the Stack

At the end, the wizard asks:

Start Crikket now with Docker Compose [Y/n]

Answer yes if:

  • DNS already points to this VPS
  • ports 80 and 443 are open
  • Docker is installed and the daemon is running

The wizard then runs:

  • Docker Compose config validation
  • image pulls
  • up -d
  • ps

If you answer no, you can start it yourself later.

Preferred command:

./scripts/start.sh

start.sh reads the generated env files and automatically picks the correct Compose file set for:

  • bundled vs external PostgreSQL
  • built-in Caddy vs no built-in proxy

Use raw docker compose only if you intentionally want to bypass the wrapper.

Point the Domains to the VPS

In your DNS provider:

  • create an A record for app.example.com pointing to the VPS public IPv4
  • create an A record for api.example.com pointing to the VPS public IPv4

If you use IPv6, add matching AAAA records only if the VPS is actually reachable over IPv6.

Then make sure your cloud firewall or host firewall allows:

  • TCP 80
  • TCP 443

Keep these private unless you have a specific reason to expose them:

  • 3000
  • 3001
  • 5432

Verify HTTPS and Container Health

Check DNS first:

dig +short app.example.com
dig +short api.example.com

Run the built-in health check:

./scripts/healthcheck.sh

healthcheck.sh verifies:

  • expected services are running
  • app and API URLs are configured consistently
  • bundled Postgres is reachable when you use bundled database mode
  • the public app and API URLs respond successfully

For targeted live logs, use Docker Compose directly:

docker compose logs -f caddy
docker compose logs -f server

Quick HTTPS checks:

curl -I https://app.example.com
curl -I https://api.example.com

Expected result:

  • both hostnames resolve to the VPS
  • Caddy provisions certificates successfully
  • app.example.com loads the web app
  • api.example.com returns an HTTP response from the API container

Common Failure Points

  • DNS still points somewhere else, so ACME validation never reaches your VPS
  • ports 80 or 443 are blocked by the provider firewall, security group, or ufw
  • the frontend and backend domains were entered as the same hostname
  • storage credentials are missing, so uploads fail later even if the app boots
  • BETTER_AUTH_COOKIE_DOMAIN needs manual adjustment if your frontend and backend do not share a normal parent domain

If You Need to Restart Later

./scripts/restart.sh
./scripts/healthcheck.sh

For upgrades later, use:

./scripts/update.sh

On this page