> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trypost.it/llms.txt
> Use this file to discover all available pages before exploring further.

# Docker

> Run TryPost with Docker Compose

# Docker

TryPost ships two Compose files:

| File | Purpose |
| - | - |
| `compose.prod.yaml` | **Self-hosting in production** — pulls `ghcr.io/trypostit/trypost:latest`, Postgres, Redis, Horizon, Reverb, scheduler, optional Caddy TLS |
| `compose.yaml` | Local development — builds from source, bind-mounts the repo, Mailpit, Vite |

## 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:

```yaml theme={null}
services:
  app:
    environment:
      DB_CONNECTION: mysql
      DB_HOST: your-mysql-host
      DB_PORT: "3306"
      DB_DATABASE: trypost
      DB_USERNAME: trypost
      DB_PASSWORD: your_password
    depends_on:
      redis:
        condition: service_healthy
```

Then delete the `pgsql:` service itself and the `pgdata` volume it declared.

<Warning>
  **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:

  ```bash theme={null}
  docker compose -f compose.prod.yaml exec app php -m | grep pdo_mysql
  ```

  No output means you need a newer image.
</Warning>

<Note>
  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.
</Note>

See [Database](/self-hosting/configuration#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

| You (once) | Entrypoint / Supervisor (automatic) |
| - | - |
| Set `APP_KEY`, `APP_URL`, DB passwords, `REVERB_APP_SECRET`, **`PASSPORT_PRIVATE_KEY` / `PASSPORT_PUBLIC_KEY`** | Wait for the database, run migrations |
| `docker compose … up -d` | Use Passport keys from env, `storage:link` if missing, ensure Personal Access Client |
| `db:seed` (plans) + `UserSeeder` | Cache config/routes/views/events |
| Optional: Caddy TLS profile | Start nginx, php-fpm, **Horizon**, **Reverb**, **schedule:work** |

### 1. Clone the repository

```bash theme={null}
git clone https://github.com/trypostit/trypost.git
cd trypost
```

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

```bash theme={null}
docker compose -f compose.prod.yaml run --rm app php artisan key:generate --show
```

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_KEY` — **required in production** (file keys under `storage/` are not on a persisted volume). Generate once:

```bash theme={null}
docker compose -f compose.prod.yaml run --rm app php artisan passport:keys --show
```

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](/self-hosting/configuration).

### 3. Start the stack

```bash theme={null}
docker compose -f compose.prod.yaml up -d
```

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)

```bash theme={null}
docker compose -f compose.prod.yaml exec app php artisan db:seed
docker compose -f compose.prod.yaml exec app php artisan db:seed --class=UserSeeder
```

| Seeder | Purpose |
| - | - |
| `DatabaseSeeder` | Plan rows (+ idempotent Passport Personal Access Client) |
| `UserSeeder` | Admin (`admin@trypost.it` / `password`) |

Change the admin password on first login. Extra accounts come from workspace invites (Settings → Members).

<Tip>
  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.
</Tip>

### 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:

```bash theme={null}
docker compose -f compose.prod.yaml --profile proxy up -d
```

Without the profile, the app listens on `http://localhost:8000`.

### 6. Verify

```bash theme={null}
docker compose -f compose.prod.yaml exec app php artisan horizon:status
docker compose -f compose.prod.yaml logs -f app
```

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

| Service | URL |
| - | - |
| TryPost | `http://localhost:8000` (or your `APP_URL`) |
| Reverb | port `8080`, proxied under `/app/` and `/apps/` by in-container nginx |

### Reverb on a custom domain

The published image bakes `VITE_REVERB_*` at build time (`localhost:8080`). For HTTPS on your domain, rebuild:

```bash theme={null}
docker build \
  --build-arg VITE_REVERB_HOST=post.yourdomain.com \
  --build-arg VITE_REVERB_PORT=443 \
  --build-arg VITE_REVERB_SCHEME=https \
  --build-arg VITE_REVERB_APP_KEY=trypost-reverb-key \
  -t trypost-app:custom \
  -f docker/Dockerfile \
  --target production \
  .
```

Point `compose.prod.yaml` at that image. Changing only runtime `REVERB_HOST` / `PORT` / `SCHEME` does not update the browser client.

### Common production commands

```bash theme={null}
docker compose -f compose.prod.yaml logs -f app
docker compose -f compose.prod.yaml exec app php artisan horizon:status
docker compose -f compose.prod.yaml exec app php artisan migrate --force
docker compose -f compose.prod.yaml down
```

<Note>
  Running `migrate --force` to move an existing instance to a newer version? Back the database up first and check [Upgrading](/self-hosting/upgrading) — some releases rewrite data as well as schema.
</Note>

***

## Local development

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

### 1. Clone and start

```bash theme={null}
git clone https://github.com/trypostit/trypost.git
cd trypost
docker compose up -d
```

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

```bash theme={null}
docker compose exec app php artisan db:seed
docker compose exec app php artisan db:seed --class=UserSeeder
```

Or wipe-and-rebuild on a brand-new database:

```bash theme={null}
docker compose exec app php artisan migrate:fresh --seed
docker compose exec app php artisan db:seed --class=UserSeeder
```

<Warning>
  `migrate:fresh` **drops every table**.
</Warning>

| Field | Value |
| - | - |
| Email | `admin@trypost.it` |
| Password | `password` |

### Access (dev)

| Service | URL |
| - | - |
| TryPost | `http://localhost:8000` (or `APP_PORT`) |
| Mailpit | `http://localhost:8025` |
| Vite HMR | `http://localhost:5173` |
| Postgres | `localhost:5432` |
| Redis | `localhost:6379` |
| Reverb | `localhost:8080` |

### Common development commands

```bash theme={null}
docker compose up -d
docker compose down
docker compose logs -f app
docker compose exec app php artisan <command>
```

## Troubleshooting

### Port conflicts

```env theme={null}
APP_PORT=8080
FORWARD_DB_PORT=5433
FORWARD_MAILPIT_UI_PORT=8026
```

### Permission issues (Linux)

```bash theme={null}
sudo chown -R $USER: .
```

### Database connection refused

```bash theme={null}
docker compose ps
# or
docker compose -f compose.prod.yaml ps
```

### `could not find driver` on MySQL

The image predates the MySQL driver. Check and pull a newer one:

```bash theme={null}
docker compose -f compose.prod.yaml exec app php -m | grep pdo_mysql
```

Images up to and including `v1.0.8` shipped `pdo_pgsql` only — see [above](#which-database-compose-gives-you).

### 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:

```bash theme={null}
docker compose -f compose.prod.yaml exec app php artisan db:seed
```

### Horizon not processing jobs

```bash theme={null}
docker compose -f compose.prod.yaml exec app php artisan horizon:status
docker compose -f compose.prod.yaml logs app | grep -i horizon
```

## Next steps

* [Configuration](/self-hosting/configuration) — mail, storage, social OAuth, AI
* [Production setup](/self-hosting/production) — bare-metal Nginx / Supervisor if you are not using `compose.prod.yaml`
* [Installation](/self-hosting/installation) — clone-and-install without Docker


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.