Skip to main content

Docker

TryPost ships two Compose files:

Which database Compose gives you

Both files bundle PostgreSQL 16. That is the packaged path, and the one TryPost Cloud runs. MySQL is equally supported by the application, but no Compose file ships it. To run TryPost on MySQL with Docker, point the app at your own MySQL and take the bundled Postgres out of the file — including the depends_on entry, or Compose refuses to start on a service that no longer exists:
Then delete the pgsql: service itself and the pgdata volume it declared.
Check the image ships the MySQL driver before you switch. Images up to and including v1.0.8 were built with pdo_pgsql only, so DB_CONNECTION=mysql fails with could not find driver before a single query runs. Confirm with:
No output means you need a newer image.
The container’s readiness check uses pg_isready, so on MySQL it cannot confirm the database is up. It gives up after 60 seconds and continues, and migrations then run normally — so every boot takes about a minute longer. Nothing is lost; the container just waits out the check first.
See Database for the trade-offs between the two engines.

Requirements

  • Docker Desktop (Mac, Windows) or Docker Engine (Linux)
  • Docker Compose v2

Production self-hosting

No local PHP/Node install required. The published image includes the built frontend.

What you do vs what the container does

1. Clone the repository

You only need the repo for compose.prod.yaml (and optionally Caddyfile). The app image comes from GHCR.

2. Generate an app key and edit the compose file

Paste the value into APP_KEY in compose.prod.yaml. Also set:
  • APP_URL — your public URL (e.g. https://post.yourdomain.com)
  • DB_PASSWORD / POSTGRES_PASSWORD (must match)
  • REVERB_APP_SECRET
  • PASSPORT_PRIVATE_KEY / PASSPORT_PUBLIC_KEYrequired in production (file keys under storage/ are not on a persisted volume). Generate once:
Paste the PEM contents into the compose env vars (use \n for newlines). Do not regenerate after users have issued API or MCP tokens. Leave REVERB_APP_KEY as trypost-reverb-key unless you rebuild the image — it must match the key baked into the published frontend bundle. Queues, cache, session, and broadcast are already set to Redis / Reverb / database in compose.prod.yaml. Configure mail (defaults to log) and optional social/AI credentials in the same file — see Configuration.

3. Start the stack

On first boot the entrypoint:
  • waits for the database (PostgreSQL only — see above if you run MySQL)
  • runs migrations
  • creates storage:link if missing (unused if you later switch to S3 / R2 / Spaces)
  • loads Passport keys from PASSPORT_PRIVATE_KEY / PASSPORT_PUBLIC_KEY (exits if unset in production)
  • ensures the Passport Personal Access Client (PassportSeeder)
  • caches config / routes / views / events
  • starts nginx, php-fpm, Horizon, Reverb, and schedule:work
It does not create the admin user or plan rows — next step.

4. Seed plans and admin user (one-time)

Change the admin password on first login. Extra accounts come from workspace invites (Settings → Members).
Production Passport keys come from PASSPORT_* env vars (set before up). The entrypoint also runs PassportSeeder so API-key creation works without a manual client step. MCP uses OAuth (mcp:use) with those same keys — not API-key Bearer headers. Still run db:seed / UserSeeder for plans and the admin user.

5. Optional HTTPS with Caddy

  1. Point DNS at the host
  2. Set APP_URL to https://your-domain and APP_DOMAIN on the caddy service
  3. Start with the proxy profile:
Without the profile, the app listens on http://localhost:8000.

6. Verify

  1. Open APP_URL and sign in as admin
  2. Create an API key (Settings → API Keys) — proves Passport keys + Personal Access Client exist
  3. Connect an MCP client via OAuth (Settings → MCP), if you use assistants
  4. Schedule a post — Horizon must show as running

Access

Reverb on a custom domain

The published image bakes VITE_REVERB_* at build time (localhost:8080). For HTTPS on your domain, rebuild:
Point compose.prod.yaml at that image. Changing only runtime REVERB_HOST / PORT / SCHEME does not update the browser client.

Common production commands

Running migrate --force to move an existing instance to a newer version? Back the database up first and check Upgrading — some releases rewrite data as well as schema.

Local development

Use compose.yaml when you want a bind-mounted source tree.

1. Clone and start

On first boot the entrypoint can seed .env from docker/.env.docker.example, install Composer/npm deps if missing, generate APP_KEY, migrate, storage:link, and passport:keys. Horizon, Reverb, the scheduler, and Vite start via Supervisor. For local media URLs, set FILESYSTEM_DISK=public in .env (the Docker example ships with local) and ensure storage:link ran.

2. Seed admin + Passport client

Or wipe-and-rebuild on a brand-new database:
migrate:fresh drops every table.

Access (dev)

Common development commands

Troubleshooting

Port conflicts

Permission issues (Linux)

Database connection refused

could not find driver on MySQL

The image predates the MySQL driver. Check and pull a newer one:
Images up to and including v1.0.8 shipped pdo_pgsql only — see above.

Boot hangs for a minute on MySQL

Expected. The readiness check is pg_isready, so it cannot confirm a MySQL server is up; it gives up after 60 seconds and migrations run normally. Watch it happen with docker compose -f compose.prod.yaml logs -f app.

API key creation fails with “Personal access client not found”

Seed the Passport client:

Horizon not processing jobs

Next steps