Initial commit: Pireon Admin — headless admin panel and public API
Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
143
docker-compose.yml
Normal file
143
docker-compose.yml
Normal file
@@ -0,0 +1,143 @@
|
||||
###############################################################################
|
||||
# Production stack.
|
||||
#
|
||||
# Four containers and one database. Deliberately no Redis: Postgres holds the
|
||||
# cache, the sessions and the queue, which for a site this size is plenty and
|
||||
# is one less thing to run, back up and forget to secure. Swapping it in later
|
||||
# is three environment variables.
|
||||
#
|
||||
# docker compose --env-file .env.production up -d --build
|
||||
#
|
||||
# The web container is the only one that migrates, so a deploy runs them once
|
||||
# rather than once per replica.
|
||||
###############################################################################
|
||||
|
||||
x-app: &app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: pireon-admin:latest
|
||||
restart: unless-stopped
|
||||
env_file: .env.production
|
||||
environment:
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
|
||||
# The maintenance flag and the health badge are read on nearly every
|
||||
# request, so the cache has to be shared by every container — a per-process
|
||||
# one would have the panel and the API disagreeing about whether the site
|
||||
# is down.
|
||||
CACHE_STORE: database
|
||||
SESSION_DRIVER: database
|
||||
QUEUE_CONNECTION: database
|
||||
volumes:
|
||||
# Uploaded images live here, and they are the one thing in this stack that
|
||||
# cannot be rebuilt from the repository.
|
||||
- storage:/app/storage
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
services:
|
||||
app:
|
||||
<<: *app
|
||||
environment:
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
CACHE_STORE: database
|
||||
SESSION_DRIVER: database
|
||||
QUEUE_CONNECTION: database
|
||||
|
||||
# Only here: migrations run once per deploy, not once per container.
|
||||
RUN_MIGRATIONS: "true"
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:80"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://localhost/up"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 40s
|
||||
retries: 3
|
||||
|
||||
# Image processing and the inbox notifications. Without this, uploads serve
|
||||
# without dimensions or srcset and the panel's bell stays silent — the site
|
||||
# keeps working, which is exactly why the absence is easy to miss.
|
||||
worker:
|
||||
<<: *app
|
||||
command: ["php", "artisan", "queue:work", "--tries=3", "--max-time=3600", "--sleep=3"]
|
||||
|
||||
# Waits for the web container, not just the database: it is the one that
|
||||
# migrates, and a worker that starts against an empty schema spends its
|
||||
# first minute crash-looping on tables that do not exist yet.
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
app:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "php", "artisan", "queue:monitor", "default"]
|
||||
interval: 60s
|
||||
timeout: 10s
|
||||
start_period: 40s
|
||||
retries: 3
|
||||
stop_grace_period: 30s
|
||||
|
||||
# Closes expired calls, sweeps up images the queue missed and refreshes the
|
||||
# git repositories. `schedule:work` is the long-running form of a crontab.
|
||||
scheduler:
|
||||
<<: *app
|
||||
command: ["php", "artisan", "schedule:work"]
|
||||
|
||||
# The FrankenPHP base image ships a healthcheck that probes Caddy's admin
|
||||
# API, and this container does not serve HTTP — inherited, it could never
|
||||
# pass. Replaced rather than disabled, because `compose up --wait` refuses
|
||||
# a service with no healthcheck at all.
|
||||
#
|
||||
# What it proves is modest and stated plainly: the application still boots
|
||||
# and can read its schedule. It does not prove the loop is running; if that
|
||||
# dies, the process dies with it and `restart: unless-stopped` brings it
|
||||
# back.
|
||||
healthcheck:
|
||||
test: ["CMD", "php", "artisan", "schedule:list"]
|
||||
interval: 60s
|
||||
timeout: 15s
|
||||
start_period: 40s
|
||||
retries: 3
|
||||
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
app:
|
||||
condition: service_healthy
|
||||
stop_grace_period: 30s
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_DATABASE:-pireon}
|
||||
POSTGRES_USER: ${DB_USERNAME:-pireon}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env.production}
|
||||
|
||||
# Content is written by a handful of people and read constantly; the
|
||||
# default collation rules are what the JSON slug lookups sort under.
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-pireon} -d ${DB_DATABASE:-pireon}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
|
||||
# Not published: nothing outside this stack has any business reaching the
|
||||
# database. Use `docker compose exec postgres psql` to get in.
|
||||
expose:
|
||||
- "5432"
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
storage:
|
||||
Reference in New Issue
Block a user