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.
Try it locally
Section titled “Try it locally”On your own machine, with no proxy, domain, certificate or mail. From
apps/tymeslot/:
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 -dIt 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.
Installation
Section titled “Installation”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.
-
Copy the environment file:
Terminal window cp .env.example .envSet
APP_TRAEFIK_HOST,TZand theEMAIL_FROM_*values. The SMTP block is what sends confirmations and reminders — with the defaultEMAIL_ADAPTER=testthe application runs and every message is discarded without an error. -
Generate the secrets:
Terminal window mkdir -p .secrets volumes/postgres volumes/dataopenssl rand -base64 64 | tr -d '\n' > .secrets/secret_key_base.txtopenssl rand -base64 48 | tr -d '\n' > .secrets/data_encryption_key.txtopenssl rand -hex 32 | tr -d '\n' > .secrets/db_pwd.txttouch .secrets/smtp_password.txtchmod 700 .secrets && chmod 600 .secrets/*.txtdata_encryption_key.txtencrypts the calendar credentials users connect later. Keep it with the backups and never rotate it. -
Start it:
Terminal window docker compose up -d -
Over the VPN, open
https://<APP_TRAEFIK_HOST>and register. The first account becomes the admin. Then close registration withREGISTRATION_ENABLED=falsein.envanddocker compose up -d. The switch at/adminhas no effect while the variable is set: the environment outranks the saved setting.
Verify
Section titled “Verify”docker compose ps # both services healthycurl -fsSI https://<APP_TRAEFIK_HOST>/ # 302 to the login pageRegister, 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.
Going further
Section titled “Going further”Who may reach the booking page
Section titled “Who may reach the booking page”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.
What works over a VPN alone
Section titled “What works over a VPN alone”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.
Headers the proxy sets
Section titled “Headers the proxy sets”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.
Backup
Section titled “Backup”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.
Updates
Section titled “Updates”Releases are frequent — eight in the first week of September 2026 — and every one migrates the database at boot.
-
Read the release notes for data changes, and diff
start-docker.shbetween the pinned and the new tag; the commands are inUPSTREAM.md. The secret-injection entrypoint relies on that script forwarding the environment. -
Back up the database and
volumes/data. -
Set
APP_TAGto the new-slimtag with its digest, then:Terminal window docker compose pull && docker compose up -ddocker compose logs tymeslot-app | grep -E 'migrations completed|Running TymeslotWeb'
Troubleshooting
Section titled “Troubleshooting”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.
Verification details
Section titled “Verification details”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
Repository files
Section titled “Repository files”apps/tymeslot/README.md— setup, mail, integrations, security model, backupapps/tymeslot/UPSTREAM.md— source, licence, upgrade checklist
Official sources
Section titled “Official sources”© 2026 SecDockBlueCode Apache-2.0Site content CC BY-NC 4.0