Deployment
Tidetime is deployed with Docker. The fastest path is the install script; the manual Compose steps below give you full control.
Install script
Section titled “Install script”On a fresh Linux server (as root, or a user with sudo):
curl -fsSL https://raw.githubusercontent.com/Sulaiman-Dauda/Tidetime/main/install.sh | bashThe script installs everything it needs — Docker, git, and openssl if missing — adds a swap file on low-memory hosts, opens firewall ports 80 and 443, then downloads the project, generates the required secrets, writes a .env file, starts the stack, and waits until it is healthy. It uses a prebuilt image published to the GitHub Container Registry (ghcr.io/sulaiman-dauda/tidetime) and only builds from source when a pull is not possible. It prints the address to open when done, for example http://<your-server-ip>. Review the script before running it if you prefer.
The instance is reachable on port 80 through Caddy (http://<your-server-ip>, no port suffix); the application’s own port stays bound to localhost. Attach a domain for HTTPS (see below) — a bare IP cannot be issued a certificate.
Required configuration
Section titled “Required configuration”Set APP_URL, DATABASE_URL, a URL-safe random POSTGRES_PASSWORD, a random AUTH_SECRET
of at least 32 characters, and a separate CRON_SECRET of at least 32 characters. The
production Compose file intentionally refuses to start with a default database password.
Set Google OAuth credentials only if providers will connect Google Calendar.
Outgoing email is configured in the administrator UI after startup. Tidetime
can retain both a generic SMTP connection and a Microsoft 365 connection; the
administrator explicitly chooses which one is active.
Microsoft 365 email
Section titled “Microsoft 365 email”Microsoft 365 email uses delegated Microsoft Graph access for one connected
work or school mailbox. Client secrets and OAuth tokens are encrypted at rest
using AUTH_SECRET; changing AUTH_SECRET invalidates the stored credentials.
- Sign in to the Microsoft Entra admin center and create an App registration using Accounts in this organizational directory only.
- Under Authentication, add a Web platform and paste the App Callback URL displayed in Dashboard → Connections → Email delivery → Microsoft 365.
- Under API permissions → Microsoft Graph → Delegated permissions, add
Mail.SendandUser.Read.openid,profile,email, andoffline_accessare requested automatically during sign-in. - Under Certificates & secrets, create a client secret. Copy the secret Value immediately; the Secret ID is not the credential.
- From the registration’s Overview page, copy its Directory (tenant) ID and Application (client) ID. Paste those and the client-secret value into Tidetime, save, and select Connect Microsoft 365.
- Sign in as the dedicated sending mailbox, send a test email, then select Use Microsoft 365.
The registered redirect URI must exactly match Tidetime’s displayed callback
URL, including https, hostname, and path. Configure the final APP_URL or
custom domain before connecting. Use a dedicated licensed/shared sending
mailbox appropriate to the organisation’s Exchange configuration, rotate the
client secret before it expires, and reconnect after changing the app
registration or public domain.
Do not expose PostgreSQL publicly. The production Compose file binds the application port to localhost so remote traffic passes through Caddy, which normalizes client-address headers before the application applies rate limits. Terminate TLS at Caddy or another trusted reverse proxy and back up the PostgreSQL volume before upgrades.
Containers
Section titled “Containers”docker-compose.prod.yml runs PostgreSQL, the standalone Next.js server, a small cron worker,
and Caddy. The app runs the checked-in Drizzle migration before it starts. The cron worker sends
Authorization: Bearer <CRON_SECRET> to POST /api/cron for webhook retries and retention cleanup.
docker compose -f docker-compose.prod.yml up -d --builddocker compose -f docker-compose.prod.yml psdocker compose -f docker-compose.prod.yml logs -f app jobsUpdates
Section titled “Updates”Tidetime shows admins a dashboard banner when a newer version is available. To update manually, pull the new image and restart:
cd /path/to/tidetime # the install directorydocker compose -f docker-compose.prod.yml pulldocker compose -f docker-compose.prod.yml up -dYour data is safe: PostgreSQL and uploads live in named volumes that survive the restart. Back up first anyway (see below).
Enabling one-click updates (optional)
Section titled “Enabling one-click updates (optional)”To make the dashboard’s Update now button perform the update for you, enable the updater service. It runs a small helper container with access to the Docker socket, which is host-root-equivalent — only enable it if you accept that trade-off.
Run it alongside the production stack, from the install directory:
docker compose -f docker-compose.prod.yml -f docker-compose.updater.yml up -dThe app talks to the updater over a private volume; it never gets Docker access itself. To disable it again:
docker compose -f docker-compose.prod.yml -f docker-compose.updater.yml stop updaterdocker compose -f docker-compose.prod.yml up -dBackups and restore
Section titled “Backups and restore”Create a compressed logical backup before every upgrade and copy it off the application host:
mkdir -p backupsdocker compose -f docker-compose.prod.yml exec -T postgres \ sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom --no-owner --no-acl' \ > "backups/tidetime-$(date -u +%Y%m%dT%H%M%SZ).dump"Test restoration regularly against a separate empty database. The following command overwrites the named restore database, so never point it at the live database:
docker compose -f docker-compose.prod.yml exec -T postgres \ sh -c 'createdb -U "$POSTGRES_USER" tidetime_restore_test'docker compose -f docker-compose.prod.yml exec -T postgres \ sh -c 'pg_restore -U "$POSTGRES_USER" -d tidetime_restore_test --clean --if-exists --no-owner --no-acl' \ < backups/your-backup.dumpdocker compose -f docker-compose.prod.yml exec -T postgres \ sh -c 'psql -U "$POSTGRES_USER" -d tidetime_restore_test -c "select count(*) from users;"'Release checks
Section titled “Release checks”Before deployment run npm ci && npm run check, then test setup, provider invitation, service
creation, provider-specific booking, automatic-provider booking, cancellation, Google Calendar,
email, and a Zapier catch hook against a staging database.
Pre-release database policy
Section titled “Pre-release database policy”This pre-release build ships its migrations in drizzle/ (applied in order on boot) and
intentionally provides no upgrade path from earlier prototypes. Use a new empty PostgreSQL
database. This policy can change once the first production release creates a compatibility
commitment.