Skip to content

Tymeslot

Tymeslot gives people a page where they book time with you. They pick a free slot, it lands in your calendar — Google, Outlook, Apple, Nextcloud or any CalDAV server — and both sides get a confirmation, later a reminder. Video links for Meet, Teams, Zoom or your own room come with it. Two containers: the application and PostgreSQL.

On your own machine, with no proxy, domain, certificate or mail. From apps/tymeslot/:

Terminal window
cp .env.local.example .env.local
# The secrets ship empty; generate the ones this stack needs
{
echo "DB_PASSWORD=$(openssl rand -hex 32)"
echo "SECRET_KEY_BASE=$(openssl rand -base64 64 | tr -d '\n')"
echo "DATA_ENCRYPTION_KEY=$(openssl rand -base64 48 | tr -d '\n')"
} >> .env.local
docker compose -f docker-compose.local.yml --env-file .env.local up -d

It listens on 127.0.0.1:4000 only. The first boot runs the migrations, about thirty seconds. Open http://localhost:4000, register — the first account is the admin — create a meeting type, and open its booking page in a private window. Mail is discarded here, so no confirmation is sent.

For a deployment on a server. From apps/tymeslot/ — the stack routes through Traefik, so have it running with a hostname pointing at the host.

  1. Copy the environment file:

    Terminal window
    cp .env.example .env

    Set APP_TRAEFIK_HOST, TZ and the EMAIL_FROM_* values. The SMTP block is what sends confirmations and reminders — with the default EMAIL_ADAPTER=test the application runs and every message is discarded without an error.

  2. Generate the secrets:

    Terminal window
    mkdir -p .secrets volumes/postgres volumes/data
    openssl rand -base64 64 | tr -d '\n' > .secrets/secret_key_base.txt
    openssl rand -base64 48 | tr -d '\n' > .secrets/data_encryption_key.txt
    openssl rand -hex 32 | tr -d '\n' > .secrets/db_pwd.txt
    touch .secrets/smtp_password.txt
    chmod 700 .secrets && chmod 600 .secrets/*.txt

    data_encryption_key.txt encrypts the calendar credentials users connect later. Keep it with the backups and never rotate it.

  3. Start it:

    Terminal window
    docker compose up -d
  4. Over the VPN, open https://<APP_TRAEFIK_HOST> and register. The first account becomes the admin. Then close registration with REGISTRATION_ENABLED=false in .env and docker compose up -d. The switch at /admin has no effect while the variable is set: the environment outranks the saved setting.

Terminal window
docker compose ps # both services healthy
curl -fsSI https://<APP_TRAEFIK_HOST>/ # 302 to the login page

Register, create a meeting type, open its booking page in a private window and book a slot. If mail is configured, the confirmation arrives; with Mailpit pointed at, it appears in its inbox instead.

A booking page exists for people you have not met, so a production instance carries access-public. The stack ships access-vpn — VPN only — and leaves that step to you. When you open it, keep registration closed — guests book without an account — and consider enabling CrowdSec for the deployment (CROWDSEC_BOUNCER_ENABLED in core/traefik), which then stands in front of every router.

Connecting a Google, Outlook or Zoom account works from a VPN client, because the OAuth callback is a browser redirect to this host. Two things do not: calendar push notifications, which need the provider to reach the host from the public internet — leave WEBHOOK_BASE_URL unset and the sync polls every 15 minutes — and anything delivered by inbound webhook: Stripe payments, Slack OAuth, the shared Telegram bot.

A CalDAV server or a MiroTalk room on a private address is refused by the application’s SSRF guard until ALLOW_PRIVATE_IPS_FOR_CALENDAR=true or ALLOW_PRIVATE_IPS_FOR_VIDEO=true is set. The two switches are separate so relaxing one does not open the other.

EMAIL_ADAPTER=smtp needs a host, a username and a non-empty password in .secrets/smtp_password.txt; the application refuses to boot otherwise. The port sets the encryption: 587 forces STARTTLS, 465 implicit TLS, any other port opportunistic TLS.

The booking embed does not work as shipped

Section titled “The booking embed does not work as shipped”

Booking pages can be embedded in another site (embed.js). headers-standard sends X-Frame-Options: DENY, which refuses that. If you want embeds, name the parent origins with a per-app frame-ancestors middleware on the embed routes rather than dropping the header.

The stack ships limit-standard and headers-standard. A first load is about ten requests, far below the burst of 500. The application sends its own nonce-based CSP with frame-ancestors 'none', nosniff, a referrer policy and Secure; HttpOnly; SameSite=Lax cookies; the proxy sets HSTS, X-Frame-Options: DENY and the rest of the profile, and the two agree.

The database, the uploads under volumes/data/uploads, and two secrets: secret_key_base.txt and data_encryption_key.txt. Stored calendar credentials are encrypted with them, and a restore without them leaves every integration disconnected. Restore the secrets first, then the database, then start the application — it migrates at boot, and a half-restored database would be migrated over.

The ## Backup section of the README has the borgmatic block.

Releases are frequent — eight in the first week of September 2026 — and every one migrates the database at boot.

  1. Read the release notes for data changes, and diff start-docker.sh between the pinned and the new tag; the commands are in UPSTREAM.md. The secret-injection entrypoint relies on that script forwarding the environment.

  2. Back up the database and volumes/data.

  3. Set APP_TAG to the new -slim tag with its digest, then:

    Terminal window
    docker compose pull && docker compose up -d
    docker compose logs tymeslot-app | grep -E 'migrations completed|Running TymeslotWeb'

The container exits with SECRET_KEY_BASE is missing. The secret file is empty or absent. The entrypoint refuses to start with an empty secret rather than letting the application run with one.

The container exits with EMAIL_ADAPTER=smtp is not usable. SMTP_HOST or SMTP_USERNAME is empty, or smtp_password.txt is. All three are required for the smtp adapter.

Nobody receives any mail, and nothing is logged. EMAIL_ADAPTER=test. It discards every message by design.

A connected calendar updates only every 15 minutes. Push notifications need WEBHOOK_BASE_URL and a host the provider can reach from the public internet. Polling is the fallback, and on a VPN-only host the only mode.

The healthcheck flips to unhealthy under load. /healthcheck rate-limits itself at 30 requests per minute per address. Docker’s check runs every 30 seconds; anything else polling it counts against the same budget.

Last run on a host 8 Nov 2026 · data restored from a backup

Pinned at 1.41.0-slim (released 5 Nov 2026) · the newest upstream release

In the upstream images: 1 CRITICAL · 27 HIGH with a published fix — per image