Get started in minutes
Deploy Yotara on your own server. No vendor lock-in, no data leaving your infrastructure.
Upgrading an existing instance? The Archive tab is now called Completed — your tasks and their history are unchanged.
Before you start
Self-hosting is a small, one-time setup — but it does ask a few things of you. Make sure you're comfortable with these before you begin.
Set two keys
You must generate a
BETTER_AUTH_SECRET
to sign sessions, and a free
Resend
API key for verification and password-reset emails. Without either, the stack
refuses to boot in production.
Plan for persistence
Your data lives in a SQLite file. Keep it on a named volume or mounted path so it survives container restarts.
Back it up
You own the data, which means you own the backups. Yotara runs SQLite in WAL mode
— use
sqlite3 yotara.db ".backup yotara-backup.db", or copy the database while the app is stopped, and keep backups off-server.
Expose it safely
For real use, put it behind a domain with TLS. The included nginx config serves plain HTTP on port 80. Add a TLS-terminating reverse proxy (e.g. Caddy, nginx with certbot, or Cloudflare Tunnel) in front of it.
Quick start with Docker Compose
This downloads Yotara's maintained Compose file, pinned to the current release, and starts the stack.
$ curl -o docker-compose.yml \ https://raw.githubusercontent.com/apauldev/yotara/main/docker-compose.selfhost.yml $ export BETTER_AUTH_SECRET=$(openssl rand -base64 32) $ export RESEND_API_KEY=re_your_key_here $ docker compose up -d
→ Open http://localhost:8080
The API refuses to boot without a
Resend
API key — it powers signup verification and password resets, and the free tier is plenty
for personal use. Once you've verified a sender domain with Resend, set
EMAIL_FROM
to match it.
Manual Docker Compose
Create a
docker-compose.yml
in your project directory:
services: api: image: apauldev2/yotara-api:v0.85.3 environment: DATABASE_URL: /app/apps/api/data/yotara.db APP_BASE_URL: ${APP_BASE_URL:-http://localhost:8080} FRONTEND_BASE_URL: ${FRONTEND_BASE_URL:-http://localhost:8080} TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-http://localhost:8080} TRUST_PROXY: ${TRUST_PROXY:-172.16.0.0/12} BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET?set a strong BETTER_AUTH_SECRET (openssl rand -base64 32)} HOST: 0.0.0.0 PORT: ${PORT:-3000} NODE_ENV: ${NODE_ENV:-production} RESEND_API_KEY: ${RESEND_API_KEY:-} EMAIL_FROM: ${EMAIL_FROM:-noreply@email.yotara.website} CONTENT_SECURITY_POLICY: ${CONTENT_SECURITY_POLICY:-default-src 'self'; script-src 'self' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self' https://cloudflareinsights.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'} volumes: - yotara_api_data:/app/apps/api/data security_opt: - no-new-privileges:true cap_drop: - ALL read_only: true tmpfs: - /tmp frontend: image: apauldev2/yotara-frontend:v0.85.3 depends_on: - api environment: CONTENT_SECURITY_POLICY: ${CONTENT_SECURITY_POLICY:-default-src 'self'; script-src 'self' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self' https://cloudflareinsights.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'} ports: - "8080:80" volumes: yotara_api_data:
Then run:
$ export BETTER_AUTH_SECRET=$(openssl rand -base64 32) $ export RESEND_API_KEY=re_your_key_here $ docker compose up -d
→ Open http://localhost:8080
System requirements
That's it — no external database, no Redis, and no mail server to run (email delivery
uses Resend's free tier). Generate your
BETTER_AUTH_SECRET
with
openssl rand -base64 32
and export it, along with your
RESEND_API_KEY, before
docker compose up.
Production checklist
Happy with the quick start? Before you rely on it day to day, run through this list.
- Use a strong, unique BETTER_AUTH_SECRET and keep it out of version control and shell history.
- Serve over HTTPS with a real domain and a valid certificate — the stack ships security headers, but TLS is on you.
- Persist the database on a named volume (the Compose file does this) or a backed-up mount.
-
Automate backups with
sqlite3 yotara.db ".backup yotara-backup.db"(WAL-safe) or a copy taken while the app is stopped, and test a restore occasionally. - Pin your image tag for reproducible upgrades, then review the changelog before bumping.
- Restrict network access with a firewall; only expose what you intend.