feat(docker): add multi-stage Dockerfile with dev and production targets

Introduces a self-contained Docker build under docker/ that boots the
full Laravel + Vite + Reverb + Horizon + scheduler stack inside a
single container.

The Dockerfile exposes two targets sharing a common system-base layer
(PHP 8.4-FPM Alpine + Postgres/Redis/intl/sockets/redis extensions):

  - dev: bind-mount source at runtime, runs Vite via supervisord, hot
    reloads PHP via opcache.validate_timestamps=1, UID/GID build args
    align container writes with the host user.

  - production: ships the prebuilt application — composer --no-dev,
    npm run build + build:ssr, wayfinder TS pre-generated, OpCache
    hardened, fixed UID 1000.

Sidecar configs (nginx, php.{dev,prod}.ini, supervisord.{dev,prod}.conf,
entrypoint, postgres-init for the test DB) live next to the Dockerfile
so the build context is self-describing. The entrypoint is idempotent
and handles APP_KEY generation, migrations, storage:link, Passport
keys, Wayfinder regen, and dependency reinstall on every boot.
This commit is contained in:
André Dantas 2026-05-08 23:00:23 -03:00
parent 200d34fadb
commit 4ce5c9173f
9 changed files with 677 additions and 0 deletions

177
docker/.env.docker.example Normal file
View file

@ -0,0 +1,177 @@
APP_NAME="TryPost"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
# Self-hosted mode (skips payment requirements)
SELF_HOSTED=true
TELESCOPE_ENABLED=false
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
# Database — service name "pgsql" inside the Compose network.
DB_CONNECTION=pgsql
DB_HOST=pgsql
DB_PORT=5432
DB_DATABASE=trypost
DB_USERNAME=postgres
DB_PASSWORD=password
# Session
SESSION_DRIVER=database
SESSION_LIFETIME=1440
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
# Broadcasting, Queue, Cache — all Redis-backed in Docker.
BROADCAST_CONNECTION=reverb
QUEUE_CONNECTION=redis
CACHE_STORE=redis
# File Storage — local-disk by default; switch to s3/r2 for production.
FILESYSTEM_DISK=local
# Redis — service name "redis" inside the Compose network.
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
# Mail — service name "mailpit" inside the Compose network. UI at :8025.
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
# Reverb (WebSockets) — host=localhost is browser-perspective via host port-forward.
REVERB_APP_ID=1001
REVERB_APP_KEY=trypost-reverb-key
REVERB_APP_SECRET=trypost-reverb-secret
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=http
# AWS S3 (set FILESYSTEM_DISK=s3)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_URL=
# Cloudflare R2 (set FILESYSTEM_DISK=r2)
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_ENDPOINT=
R2_REGION=auto
R2_BUCKET=
R2_URL=
# ============================================
# Social Platform Credentials (optional in dev)
# ============================================
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=
LINKEDIN_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin/callback"
LINKEDIN_PAGE_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin-page/callback"
X_CLIENT_ID=
X_CLIENT_SECRET=
X_CLIENT_REDIRECT="${APP_URL}/accounts/x/callback"
TIKTOK_CLIENT_ID=
TIKTOK_CLIENT_SECRET=
TIKTOK_CLIENT_REDIRECT="${APP_URL}/accounts/tiktok/callback"
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
FACEBOOK_CLIENT_REDIRECT="${APP_URL}/accounts/facebook/callback"
INSTAGRAM_CLIENT_ID=
INSTAGRAM_CLIENT_SECRET=
INSTAGRAM_CLIENT_REDIRECT="${APP_URL}/accounts/instagram/callback"
THREADS_CLIENT_ID=
THREADS_CLIENT_SECRET=
THREADS_CLIENT_REDIRECT="${APP_URL}/accounts/threads/callback"
GOOGLE_AUTH_ENABLED=false
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CLIENT_REDIRECT="${APP_URL}/accounts/youtube/callback"
GOOGLE_AUTH_CALLBACK="${APP_URL}/auth/google/callback"
GITHUB_AUTH_ENABLED=false
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_AUTH_CALLBACK="${APP_URL}/auth/github/callback"
PINTEREST_CLIENT_ID=
PINTEREST_CLIENT_SECRET=
PINTEREST_CLIENT_REDIRECT="${APP_URL}/accounts/pinterest/callback"
# AI Services
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GEMINI_API_KEY=
ELEVENLABS_API_KEY=
AI_TEXT_PROVIDER=openai
AI_TEXT_MODEL=gpt-5.4
AI_IMAGE_PROVIDER=openai
AI_AUDIO_PROVIDER=elevenlabs
# Stripe (only required when SELF_HOSTED=false)
STRIPE_KEY=
STRIPE_SECRET=
STRIPE_WEBHOOK_SECRET=
STRIPE_STARTER_MONTHLY=
STRIPE_STARTER_YEARLY=
STRIPE_PLUS_MONTHLY=
STRIPE_PLUS_YEARLY=
STRIPE_PRO_MONTHLY=
STRIPE_PRO_YEARLY=
STRIPE_MAX_MONTHLY=
STRIPE_MAX_YEARLY=
# Telemetry — disabled by default in Docker dev.
NIGHTWATCH_ENABLED=false
NIGHTWATCH_TOKEN=
# Media Services
UNSPLASH_ACCESS_KEY=
UNSPLASH_SECRET_KEY=
GIPHY_API_KEY=
# Analytics (optional)
GTM_ID=
POSTHOG_ENABLED=false
POSTHOG_API_KEY=
POSTHOG_HOST=https://us.i.posthog.com
# Vite
VITE_APP_NAME="${APP_NAME}"
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
VITE_POSTHOG_ENABLED="${POSTHOG_ENABLED}"
VITE_POSTHOG_API_KEY="${POSTHOG_API_KEY}"
VITE_POSTHOG_HOST="${POSTHOG_HOST}"

179
docker/Dockerfile Normal file
View file

@ -0,0 +1,179 @@
# syntax=docker/dockerfile:1.7
# ----------------------------------------------------------------------------
# TryPost Dockerfile — multi-stage with `dev` and `production` targets.
#
# docker build --target dev -t trypost:dev -f docker/Dockerfile .
# docker build --target production -t trypost:prod -f docker/Dockerfile .
# ----------------------------------------------------------------------------
ARG PHP_VERSION=8.4
ARG NODE_VERSION=22
# ----------------------------------------------------------------------------
# Stage 1: system-base — PHP-FPM + system packages + extensions
# ----------------------------------------------------------------------------
FROM php:${PHP_VERSION}-fpm-alpine AS system-base
RUN apk add --no-cache \
nginx \
supervisor \
bash \
curl \
git \
unzip \
shadow \
tzdata \
postgresql-client \
postgresql-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
libzip-dev \
oniguruma-dev \
icu-dev \
linux-headers \
$PHPIZE_DEPS \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" \
pdo_pgsql \
pgsql \
gd \
zip \
opcache \
bcmath \
exif \
pcntl \
intl \
sockets \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& apk del $PHPIZE_DEPS \
&& rm -rf /tmp/* /var/cache/apk/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# ----------------------------------------------------------------------------
# Stage 2: composer-deps — full PHP deps (incl. dev) for tooling/asset build
# ----------------------------------------------------------------------------
FROM system-base AS composer-deps
COPY composer.json composer.lock ./
RUN composer install \
--no-scripts \
--no-autoloader \
--prefer-dist \
--no-interaction
# ----------------------------------------------------------------------------
# Stage 3: composer-deps-prod — production deps only (no dev tooling)
# ----------------------------------------------------------------------------
FROM system-base AS composer-deps-prod
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--no-scripts \
--no-autoloader \
--prefer-dist \
--no-interaction
# ----------------------------------------------------------------------------
# Stage 4: wayfinder-gen — generate TS route helpers (needs PHP + Laravel)
# ----------------------------------------------------------------------------
FROM composer-deps AS wayfinder-gen
COPY --from=composer-deps /var/www/html/vendor ./vendor
COPY . .
# Stub env so artisan can boot; overridden at runtime.
ENV APP_KEY=base64:c3R1Yi13YXlmaW5kZXItZ2VuLWtleS1mb3ItYnVpbGRpbmctYXNzZXRzMA== \
APP_ENV=production \
APP_DEBUG=false
RUN composer dump-autoload --no-scripts --optimize \
&& php artisan wayfinder:generate --with-form
# ----------------------------------------------------------------------------
# Stage 5: asset-build — Vite + Inertia SSR build
# ----------------------------------------------------------------------------
FROM node:${NODE_VERSION}-alpine AS asset-build
WORKDIR /var/www/html
COPY package.json package-lock.json ./
RUN npm ci --no-audit --no-fund
# Pull source + freshly generated wayfinder TS from previous stage.
COPY --from=wayfinder-gen /var/www/html /var/www/html
RUN npm run build && npm run build:ssr
# ----------------------------------------------------------------------------
# Stage 6: dev — local development image (bind-mount the source at runtime)
# ----------------------------------------------------------------------------
FROM system-base AS dev
ARG UID=1000
ARG GID=1000
# Node + npm for in-container Vite, npm scripts, ad-hoc tooling.
RUN apk add --no-cache nodejs npm
# Create non-root app user matching host UID/GID for clean bind-mount writes.
RUN groupmod -g "${GID}" www-data 2>/dev/null || groupadd -g "${GID}" app \
&& (id -u app >/dev/null 2>&1 || useradd -u "${UID}" -g "${GID}" -d /home/app -m -s /bin/bash app) \
&& chown -R "${UID}:${GID}" /var/www/html
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
COPY docker/php.dev.ini /usr/local/etc/php/conf.d/99-trypost.ini
COPY docker/supervisord.dev.conf /etc/supervisor/conf.d/supervisord.conf
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
COPY docker/.env.docker.example /var/www/html/.env.docker.example
RUN chmod +x /usr/local/bin/entrypoint.sh
ENV TRYPOST_TARGET=dev
EXPOSE 80 5173 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=5 \
CMD curl -fsS http://127.0.0.1/up || exit 1
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# ----------------------------------------------------------------------------
# Stage 7: production — self-contained image for self-hosters
# ----------------------------------------------------------------------------
FROM system-base AS production
# Non-root runtime user (fixed UID; overridable per orchestrator if needed).
RUN addgroup -g 1000 -S app \
&& adduser -u 1000 -S app -G app -h /home/app
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
COPY docker/php.prod.ini /usr/local/etc/php/conf.d/99-trypost.ini
COPY docker/supervisord.prod.conf /etc/supervisor/conf.d/supervisord.conf
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# Production composer deps only.
COPY --from=composer-deps-prod /var/www/html/vendor /var/www/html/vendor
# Application source + generated wayfinder TS + built assets.
COPY --from=wayfinder-gen /var/www/html /var/www/html
COPY --from=asset-build /var/www/html/public/build /var/www/html/public/build
COPY --from=asset-build /var/www/html/bootstrap/ssr /var/www/html/bootstrap/ssr
RUN composer dump-autoload --optimize --classmap-authoritative --no-scripts \
&& chown -R app:app /var/www/html/storage /var/www/html/bootstrap/cache
ENV TRYPOST_TARGET=production
EXPOSE 80 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsS http://127.0.0.1/up || exit 1
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

110
docker/entrypoint.sh Executable file
View file

@ -0,0 +1,110 @@
#!/bin/sh
# TryPost — container entrypoint. Idempotent first-run setup, then exec supervisord.
set -e
cd /var/www/html
TARGET="${TRYPOST_TARGET:-dev}"
# 1) Bootstrap .env from the Docker template on first dev boot. The bind-mount
# in dev hides /var/www/html/.env.docker.example, so prefer docker/ first.
if [ "${TRYPOST_DOCKER_BOOTSTRAP:-0}" = "1" ] && [ ! -f .env ]; then
if [ -f docker/.env.docker.example ]; then
echo "[entrypoint] seeding .env from docker/.env.docker.example"
cp docker/.env.docker.example .env
elif [ -f .env.docker.example ]; then
echo "[entrypoint] seeding .env from .env.docker.example"
cp .env.docker.example .env
fi
# Hand the seeded .env over to the host user so they can edit it.
chown "${UID:-1000}:${GID:-1000}" .env 2>/dev/null || true
fi
# 2) Skip-bootstrap escape hatch for advanced users.
if [ "${TRYPOST_SKIP_BOOTSTRAP:-0}" = "1" ]; then
echo "[entrypoint] TRYPOST_SKIP_BOOTSTRAP=1 — exec'ing supervisord without setup"
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
fi
# 3) Re-install composer deps if vendor was wiped (down -v in dev).
if [ "${TARGET}" = "dev" ] && [ ! -f vendor/autoload.php ]; then
echo "[entrypoint] vendor/ missing — running composer install"
composer install --no-interaction --prefer-dist
fi
# 4) Re-install node_modules if empty/wiped (dev only; vite needs them).
# Anonymous volumes pre-create the dir, so check for npm's lockfile instead.
if [ "${TARGET}" = "dev" ] && [ ! -f node_modules/.package-lock.json ]; then
echo "[entrypoint] node_modules/ empty — running npm ci"
npm ci --no-audit --no-fund
fi
# 5) APP_KEY — generate on first boot if blank.
if [ -f .env ] && ! grep -qE '^APP_KEY=base64:' .env; then
echo "[entrypoint] generating APP_KEY"
php artisan key:generate --force
fi
# 6) Wait for Postgres to be reachable.
DB_HOST_VALUE="${DB_HOST:-pgsql}"
DB_PORT_VALUE="${DB_PORT:-5432}"
DB_USER_VALUE="${DB_USERNAME:-postgres}"
DB_NAME_VALUE="${DB_DATABASE:-trypost}"
echo "[entrypoint] waiting for postgres at ${DB_HOST_VALUE}:${DB_PORT_VALUE}"
WAIT_ATTEMPTS=0
until pg_isready -h "${DB_HOST_VALUE}" -p "${DB_PORT_VALUE}" -U "${DB_USER_VALUE}" -d "${DB_NAME_VALUE}" >/dev/null 2>&1; do
WAIT_ATTEMPTS=$((WAIT_ATTEMPTS + 1))
if [ "${WAIT_ATTEMPTS}" -gt 60 ]; then
echo "[entrypoint] postgres not reachable after 60s — continuing anyway"
break
fi
sleep 1
done
# 7) Run migrations (graceful: succeeds even when nothing to migrate).
echo "[entrypoint] running migrations"
php artisan migrate --force --graceful || true
# 8) storage:link if missing.
if [ ! -L public/storage ]; then
echo "[entrypoint] linking storage"
php artisan storage:link --force || true
fi
# 9) Passport keys on first boot.
if [ ! -f storage/oauth-private.key ]; then
echo "[entrypoint] generating Passport keys"
php artisan passport:keys --force || true
fi
# 10) Wayfinder TS regen — Vite needs the files before it boots.
echo "[entrypoint] regenerating wayfinder helpers"
php artisan wayfinder:generate --with-form || true
# 11) Cache strategy: prod = pre-cache; dev = clear.
if [ "${TARGET}" = "production" ]; then
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
else
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan event:clear
fi
# 12) Permissions.
if [ "${TARGET}" = "production" ]; then
chown -R app:app storage bootstrap/cache
else
# Dev: ensure UID-mapped user owns runtime dirs.
APP_UID="${UID:-1000}"
APP_GID="${GID:-1000}"
chown -R "${APP_UID}:${APP_GID}" storage bootstrap/cache 2>/dev/null || true
fi
echo "[entrypoint] ready — handing off to supervisord"
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

31
docker/nginx.conf Normal file
View file

@ -0,0 +1,31 @@
server {
listen 80;
listen [::]:80;
server_name _;
root /var/www/html/public;
index index.php;
charset utf-8;
client_max_body_size 100M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_read_timeout 120s;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.(?!well-known).* {
deny all;
}
}

22
docker/php.dev.ini Normal file
View file

@ -0,0 +1,22 @@
; TryPost — PHP settings for local development.
memory_limit = 512M
upload_max_filesize = 100M
post_max_size = 100M
max_execution_time = 120
max_input_vars = 5000
; Hot-reload PHP edits without restarting FPM.
opcache.enable = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 0
; Surface errors loudly in dev.
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
log_errors = On
error_log = /proc/self/fd/2
; Xdebug optional — enable via XDEBUG_MODE in compose.override.yaml.

20
docker/php.prod.ini Normal file
View file

@ -0,0 +1,20 @@
; TryPost — PHP settings for production.
memory_limit = 256M
upload_max_filesize = 100M
post_max_size = 100M
max_execution_time = 60
max_input_vars = 5000
; OpCache hardened for production: no timestamp checks, larger interned-string buffer.
opcache.enable = 1
opcache.memory_consumption = 192
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0
opcache.interned_strings_buffer = 16
; Security
expose_php = Off
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /proc/self/fd/2

11
docker/postgres-init.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/sh
# Creates the test database expected by phpunit.xml on first Postgres boot.
# Postgres-alpine runs every *.sh / *.sql in /docker-entrypoint-initdb.d/ once,
# right after the primary database (POSTGRES_DB) is initialized.
set -e
psql -v ON_ERROR_STOP=1 --username "${POSTGRES_USER}" --dbname "${POSTGRES_DB}" <<-EOSQL
CREATE DATABASE trypost_test;
GRANT ALL PRIVILEGES ON DATABASE trypost_test TO "${POSTGRES_USER}";
EOSQL

View file

@ -0,0 +1,69 @@
[supervisord]
nodaemon=true
user=root
logfile=/dev/null
logfile_maxbytes=0
pidfile=/run/supervisord.pid
[program:php-fpm]
command=php-fpm -F
autostart=true
autorestart=true
priority=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:nginx]
command=nginx -g 'daemon off;'
autostart=true
autorestart=true
priority=20
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:reverb]
command=php /var/www/html/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=true
priority=30
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:horizon]
command=php /var/www/html/artisan horizon
autostart=true
autorestart=true
priority=30
stopwaitsecs=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:scheduler]
command=php /var/www/html/artisan schedule:work
autostart=true
autorestart=true
priority=40
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:vite]
command=/bin/sh -c "exec /usr/bin/npm run dev -- --host 0.0.0.0"
directory=/var/www/html
autostart=true
autorestart=true
startsecs=10
priority=50
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

View file

@ -0,0 +1,58 @@
[supervisord]
nodaemon=true
user=root
logfile=/dev/null
logfile_maxbytes=0
pidfile=/run/supervisord.pid
[program:php-fpm]
command=php-fpm -F
autostart=true
autorestart=true
priority=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:nginx]
command=nginx -g 'daemon off;'
autostart=true
autorestart=true
priority=20
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:reverb]
command=php /var/www/html/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=unexpected
exitcodes=0,2
priority=30
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:horizon]
command=php /var/www/html/artisan horizon
autostart=true
autorestart=true
priority=30
stopwaitsecs=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:scheduler]
command=php /var/www/html/artisan schedule:work
autostart=true
autorestart=true
priority=40
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0