Documentation

Install

Self-hosted Docker

Beacon ships as a Compose bundle: docker-compose.yml, .env.example, and HTTP on port 8000. You do not need a GitHub checkout or auth.json. A naked docker run of the GHCR image is not enough — that image is PHP-FPM only (no published HTTP port, no Nginx, no Redis, no Horizon).

Current image: ghcr.io/apxcde/beacon:0.3.3-beta. Override with BEACON_IMAGE in .env.

What docker compose up starts

  • app — the Laravel PHP-FPM application container (ghcr.io/apxcde/beacon)
  • nginx — the public HTTP entrypoint on http://localhost:8000 (override with BEACON_PORT)
  • redis — queue backend for Horizon
  • horizon — queue worker and Horizon supervisor
  • scheduler — Laravel scheduler loop
  • mysql — optional MySQL service, enabled only when you start the mysql profile

The Docker setup uses SQLite by default so a fresh install does not need a separate database container. If you prefer MySQL, switch the database environment variables and enable the MySQL profile.

Inside Docker, the app defaults to /var/www/html/storage/app/database/beacon.sqlite when SQLite is enabled. That container-only path is intentionally kept out of Laravel's DB_DATABASE in .env.example (that value is for php artisan serve).

First boot

Download the bundle from your license portal, or curl the same files:

mkdir beacon && cd beacon
curl -fsSL https://apexcode.dev/beacon/docker-compose.yml -o docker-compose.yml
curl -fsSL https://apexcode.dev/beacon/env.example -o .env.example
cp .env.example .env

echo '<token-from-your-license-portal>' | docker login ghcr.io -u apxcde-distrib --password-stdin
docker compose up -d

docker compose cp ./beacon.license app:/var/www/html/storage/app/beacon.license

Beacon is at http://localhost:8000.

If you installed via Composer (composer create-project apxcde/beacon from packages.apexcode.dev), skip the curls — docker-compose.yml and .env.example are already in the project. Copy beacon.license to storage/app/beacon.license, then cp .env.example .env and docker compose up -d from that directory.

On first boot the app container will:

  • create a persistent app key if one is not already set
  • create the SQLite database file under storage/app/database/beacon.sqlite when SQLite is enabled
  • wait for MySQL when DB_CONNECTION=mysql
  • run php artisan migrate --force
  • create the public storage symlink if needed

Database modes

SQLite default

No extra services are required:

docker compose up -d

The default compose environment points Laravel at:

  • DB_CONNECTION=sqlite
  • DB_DATABASE=/var/www/html/storage/app/database/beacon.sqlite (set by the container start script)

MySQL profile

Start Beacon with the MySQL profile and override the Laravel database connection:

DB_CONNECTION=mysql \
MYSQL_DATABASE=beacon \
MYSQL_USER=beacon \
MYSQL_PASSWORD=secret \
MYSQL_ROOT_PASSWORD=root \
  docker compose --profile mysql up -d

If you only set DB_CONNECTION=mysql, the container startup script defaults DB_DATABASE to MYSQL_DATABASE so first boot still targets a valid MySQL schema name.

The PHP container already includes both pdo_sqlite and pdo_mysql, so switching between SQLite and MySQL is just an environment change.

The bundled MySQL container is internal to the compose network by default and does not publish port 3306 to your host, which avoids conflicts with any local MySQL instance you may already be running.

Validate the install

  1. Open http://localhost:8000
  2. Complete the setup dashboard:
    • create the default workspace
    • save Twilio credentials
    • register a primary Twilio number
    • generate an API key
  3. Confirm the health endpoint responds:
curl http://localhost:8000/up
  1. Confirm the OpenAPI spec is reachable:
curl http://localhost:8000/openapi.yaml
  1. Send a test API request using the generated key:
curl -X POST http://localhost:8000/api/text \
  -H "X-API-Key: beacon_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+15551234567",
    "message": "Hello from Beacon"
  }'
  1. Connect an MCP client:
claude mcp add beacon http://localhost:8000/api/mcp \
  --transport http \
  --scope user \
  --header "X-API-Key: beacon_your_key_here"

Useful commands

# stop the stack
docker compose down

# stop the stack and remove named volumes (including the image-copied code volume)
docker compose down -v

# inspect the PHP-FPM app logs
docker compose logs -f app

# inspect Nginx logs
docker compose logs -f nginx

# inspect Horizon logs
docker compose logs -f horizon

Notes

  • The MCP endpoint is served by the Laravel app at /api/mcp; there is no separate MCP container.
  • Persistent app state lives in the beacon_storage and beacon_redis named volumes.
  • beacon_code is populated from the image on first boot so Nginx can serve /public without a local checkout. After docker compose pull, remove that volume before up so hashed frontend assets match the new image: docker compose down && docker volume rm beacon_beacon_code && docker compose up -d.
  • If you want to inject your own app key instead of using the generated one, set APP_KEY in .env before startup. Compose interpolates that value into the container environment; variables that are only in .env and not referenced in docker-compose.yml never reach Laravel.
  • Override the published port with BEACON_PORT and set APP_URL to match (for example BEACON_PORT=8080 and APP_URL=http://localhost:8080).
  • Image updates during your update window land as new tags on ghcr.io/apxcde/beacon and as Composer packages on packages.apexcode.dev. See licensing.md.

Raw markdown · llms.txt