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.ymlfor the main stackdocker-compose.caddy.ymlfor the bundled Caddy containerCaddyfilein the repo root for the bundled Caddy config./scripts/setup.shfor the install wizard.env,apps/server/.env, andapps/web/.envgenerated 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
- app domain, for example
- Ports
80and443open 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 IPIf 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.comBoth commands should return your VPS public IP.
Connect to the VPS and Clone the Repo
git clone https://github.com/redpangilinan/crikket
cd crikketRun everything else from the repository root.
Run the Install Wizard
./scripts/setup.shThe 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 domainExample answers:
Frontend domain: app.example.com
Backend/API domain: api.example.comRules 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
localhostor raw IPs
From those values, the wizard auto-builds:
NEXT_PUBLIC_APP_URL=https://app.example.comNEXT_PUBLIC_SITE_URL=https://app.example.comNEXT_PUBLIC_SERVER_URL=https://api.example.comBETTER_AUTH_URL=https://api.example.comCORS_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 certificatesUse 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:3001SERVER_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 fromdocker-compose.ymlyes: the wizard switches todocker-compose.external-db.ymland asks forDatabase 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:
.envapps/server/.envapps/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.comThe 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
80and443are open - Docker is installed and the daemon is running
The wizard then runs:
- Docker Compose config validation
- image pulls
up -dps
If you answer no, you can start it yourself later.
Preferred command:
./scripts/start.shstart.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
Arecord forapp.example.compointing to the VPS public IPv4 - create an
Arecord forapi.example.compointing 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:
300030015432
Verify HTTPS and Container Health
Check DNS first:
dig +short app.example.com
dig +short api.example.comRun the built-in health check:
./scripts/healthcheck.shhealthcheck.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 serverQuick HTTPS checks:
curl -I https://app.example.com
curl -I https://api.example.comExpected result:
- both hostnames resolve to the VPS
- Caddy provisions certificates successfully
app.example.comloads the web appapi.example.comreturns an HTTP response from the API container
Common Failure Points
- DNS still points somewhere else, so ACME validation never reaches your VPS
- ports
80or443are blocked by the provider firewall, security group, orufw - 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_DOMAINneeds 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.shFor upgrades later, use:
./scripts/update.sh