Appearance
Deployment
Table of Contents
- Overview
- Production Checklist
- Required Environment Configuration
- Redis Recommendation
- Logging Recommendation
- Choosing the Server Topology
- Deploying on a Server
- Managing the Server with systemd
- Deploying with Docker
- Running Background Processes
- Post-Deployment Tasks
- Verification Checklist
- Common Pitfalls
- Related Guides
Overview
This guide focuses on production-style deployment for Phenix applications. It does not describe the local development workflow.
For production, use the main server entrypoint:
bash
php public/index.phpIf you need multiple workers, use cluster mode:
bash
vendor/bin/cluster public/index.phpThe php ./server command is intended for development and hot reloading. It starts the file watcher and should not be the recommended production entrypoint.
Phenix deployment is controlled by two independent dimensions documented in Server:
APP_MODE: how client metadata is resolved (directorproxied)APP_SERVER_MODE: how the HTTP server process runs (singleorcluster)
These settings are independent. You can run:
direct + singledirect + clusterproxied + singleproxied + cluster
Production Checklist
- Set
APP_ENV=production - Set
APP_DEBUG=false - Set
APP_DEBUG_LEVEL=200or higher - Configure
APP_URL,APP_PORT,APP_MODE, andAPP_SERVER_MODE - Set a valid
APP_KEY - Configure database access with
DB_* - Configure Redis access with
REDIS_* - Prefer Redis for sessions, cache, rate limiting, and queues
- Run
php phenix migratewhen the release includes schema changes - Run
php phenix view:cachewhen the application serves views - Start the HTTP server with
php public/index.phporvendor/bin/cluster public/index.php - Start queue workers when queued tasks are used
- Start
php phenix schedule:workwhen cron schedules are used
Required Environment Configuration
At minimum, a production deployment should define the following settings:
dotenv
APP_ENV=production
APP_DEBUG=false
APP_DEBUG_LEVEL=200
APP_URL=https://example.com
APP_PORT=1337
APP_MODE=proxied
APP_SERVER_MODE=single
APP_KEY=base64:your-generated-key
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=phenix
DB_USERNAME=phenix
DB_PASSWORD=secret
REDIS_SCHEME=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_USERNAME=
REDIS_PASSWORD=
REDIS_DB=0
CACHE_STORE=redis
CACHE_REDIS_CONNECTION=default
RATE_LIMIT_STORE=redis
RATE_LIMIT_REDIS_CONNECTION=default
SESSION_DRIVER=redis
SESSION_CONNECTION=default
QUEUE_DRIVER=redis
REDIS_QUEUE_CONNECTION=default
REDIS_QUEUE=defaultNotes:
APP_MODE=directmeans Phenix receives client traffic directly.APP_MODE=proxiedmeans traffic arrives through a reverse proxy or load balancer.- When
APP_MODE=proxied,APP_TRUSTED_PROXIESmust resolve to a non-empty array of trusted IPs or CIDRs. Trust only known proxy ranges. APP_TRUSTED_PROXIESshould never be left empty in proxied mode and should not use overly broad ranges.APP_KEYis required by the crypto services. See Cryptography.APP_DEBUG_LEVEL=200writesinfoand higher logs. Use300or400for quieter production logging.- Use the Redis connection override variables only when you need a non-default Redis connection for a subsystem.
Redis Recommendation
Redis is the recommended production default for the Phenix features that depend on shared state.
- Sessions:
SESSION_DRIVER=rediskeeps session data available across workers and instances. - Cache:
CACHE_STORE=rediscentralizes cached data instead of keeping it only in memory or on one filesystem. - Rate limiting:
RATE_LIMIT_STORE=rediskeeps limiter counters consistent across multiple workers or nodes. - Queues:
QUEUE_DRIVER=redisprovides a shared queue backend for background work.
Redis is not mandatory, but it is the most practical production baseline when the application runs with multiple workers, multiple containers, or multiple hosts.
Logging Recommendation
Production deployments should disable debug responses and choose a log level that captures operational events without writing local diagnostic noise.
Use this baseline:
dotenv
APP_DEBUG=false
APP_DEBUG_LEVEL=200APP_DEBUG_LEVEL=200 records info, notice, warning, error, and higher severity messages. For high-volume applications, use 300 to keep warnings and errors, or 400 to keep only errors and higher severity messages.
For containers and managed platforms that collect stdout, prefer:
dotenv
LOG_CHANNEL=streamFor server deployments that write local files, use LOG_CHANNEL=file only when log rotation, permissions, and disk usage are managed by the host.
Choosing the Server Topology
Use this matrix as a quick deployment reference:
| Topology | When to use it | Notes |
|---|---|---|
direct + single | One Phenix process is exposed directly to clients | Small deployments or internal services |
direct + cluster | Multiple Phenix workers are exposed directly to clients | Useful when you want cluster workers without a reverse proxy |
proxied + single | A reverse proxy forwards traffic to one Phenix process | Common when Nginx, HAProxy, or ALB sits in front |
proxied + cluster | A reverse proxy forwards traffic to a clustered Phenix server | Suitable for higher concurrency with trusted forwarding headers |
Operational guidance:
- Choose
directonly when the Phenix socket is the public entrypoint. - Choose
proxiedonly when the request passes through trusted proxy infrastructure. - Choose
singlewhen one server process is enough. - Choose
clusterwhen you want multiple workers throughamphp/cluster.
Deploying on a Server
Typical bare-server deployment flow:
- Install the runtime requirements for the application and ensure Composer is available.
- Copy the project to the target host.
- Install dependencies for production:
bash
composer install --no-dev --optimize-autoloader- Configure the
.envfile with production values. - Ensure
APP_KEYis present before serving requests. - Run database migrations when the release requires them:
bash
php phenix migrate- If the application serves views, precompile them:
bash
php phenix view:cache- Start the HTTP server.
Single-process mode:
bash
php public/index.phpCluster mode:
bash
vendor/bin/cluster public/index.phpIf you use APP_MODE=proxied, make sure APP_TRUSTED_PROXIES is configured before boot. Phenix validates trusted proxies in proxied mode and will fail to boot if the list is empty.
Managing the Server with systemd
On Linux servers that use systemd, you can manage the long-running Phenix HTTP process with a service unit. This is recommended for bare-server deployments because Phenix runs as a persistent CLI server process and should be restarted automatically after a host reboot or process failure.
This section does not apply to Docker containers or managed platforms that already provide process supervision. It also should not use php ./server, because that command is intended for local development and hot reloading.
Create a service unit for the HTTP server:
bash
sudoedit /etc/systemd/system/phenix.serviceSingle-process example:
ini
[Unit]
Description=Phenix HTTP server
After=network.target
[Service]
WorkingDirectory=/var/www/phenix
EnvironmentFile=/var/www/phenix/.env
ExecStart=/usr/bin/php /var/www/phenix/public/index.php
User=phenix
Restart=always
RestartSec=5
LimitNOFILE=100000
[Install]
WantedBy=multi-user.targetCluster mode uses the cluster binary instead. Make sure APP_SERVER_MODE=cluster is set in the environment:
ini
ExecStart=/var/www/phenix/vendor/bin/cluster /var/www/phenix/public/index.phpAdjust /var/www/phenix, /usr/bin/php, and the phenix system user for your host. LimitNOFILE=100000 is optional, but useful for high-concurrency servers that may keep many network connections open.
After creating or changing the unit, reload systemd and start the service:
bash
sudo systemctl daemon-reload
sudo systemctl enable phenix.service
sudo systemctl start phenix.service
sudo systemctl status phenix.serviceRestart the service after deployments that change runtime code:
bash
sudo systemctl restart phenix.serviceQueue workers and the scheduler should use separate service units from the HTTP server. For example, /etc/systemd/system/phenix-queue.service can use:
ini
[Unit]
Description=Phenix queue worker
After=network.target
[Service]
WorkingDirectory=/var/www/phenix
EnvironmentFile=/var/www/phenix/.env
ExecStart=/usr/bin/php /var/www/phenix/phenix queue:work
User=phenix
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetThe scheduler can use the same structure in /etc/systemd/system/phenix-schedule.service with:
ini
ExecStart=/usr/bin/php /var/www/phenix/phenix schedule:workDeploying with Docker
Phenix applications can be deployed with Docker, add the container definition to your application when you are ready to deploy it. The recommended structure is to create the Dockerfile in the project root, and likewise the docker-compose.yml file and other configuration files should be located in a docker directory.
Create the support directory:
bash
mkdir -p dockerCreate Dockerfile in the application root:
dockerfile
ARG PHP_IMAGE=serversideup/php:8.2-cli-alpine
ARG COMPOSER_IMAGE=composer:2
FROM ${PHP_IMAGE} AS php-base
USER root
RUN install-php-extensions sockets \
&& apk add --no-cache sqlite-libs \
&& php -m | grep -qx pcntl \
&& php -m | grep -qx sockets \
&& php -m | grep -qx sqlite3
FROM ${COMPOSER_IMAGE} AS composer-bin
FROM php-base AS vendor-production
USER root
COPY --from=composer-bin /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
RUN apk add --no-cache git unzip
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--no-interaction \
--no-progress \
--prefer-dist \
--no-scripts \
--no-plugins \
--no-autoloader
COPY . .
RUN composer dump-autoload --optimize
FROM php-base AS runtime-base
USER root
WORKDIR /var/www/html
ENV APP_PORT=1337
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["php", "-r", "$$port=(int) (getenv('APP_PORT') ?: 1337); $$socket=@fsockopen('127.0.0.1', $$port, $$errno, $$errstr, 2); if (!$$socket) { fwrite(STDERR, $$errstr ?: 'connection failed'); exit(1); } fclose($$socket);"]
EXPOSE 1337
ENTRYPOINT ["/var/www/html/docker/entrypoint.sh"]
FROM runtime-base AS production
USER root
ENV APP_ENV=production
COPY --chown=www-data:www-data . /var/www/html
COPY --from=vendor-production --chown=www-data:www-data /var/www/html/vendor /var/www/html/vendor
RUN mkdir -p /var/www/html/storage/framework/logs /var/www/html/storage/framework/views /var/www/html/storage/framework/testing \
&& chown -R www-data:www-data /var/www/html/storage /var/www/html/database /var/www/html/vendor /var/www/html/docker/entrypoint.sh \
&& chmod -R u=rwX,g=rX,o=rX /var/www/html/storage /var/www/html/database \
&& chmod 750 /var/www/html/docker/entrypoint.sh
USER www-dataCreate docker/entrypoint.sh:
sh
#!/bin/sh
set -eu
echo "Starting production server..."
exec php public/index.php --host=0.0.0.0 --port="${APP_PORT:-1337}"Make the entrypoint executable:
bash
chmod +x docker/entrypoint.shBuild the production image:
bash
docker build --target production -t phenix:prod .Important runtime behavior:
- The documented production target sets
APP_ENV=production. - The documented image uses
docker/entrypoint.shas its entrypoint. - That entrypoint starts
php public/index.php --host=0.0.0.0 --port="${APP_PORT:-1337}".
Expected external services for a typical container deployment:
- database server configured through
DB_* - Redis configured through
REDIS_* - mail provider configured through
MAIL_*when the application sends mail
Container recommendations:
- Run the HTTP server container separately from queue workers.
- Run the scheduler separately from the HTTP server container.
- Mount or persist only what your deployment needs; application state for sessions, cache, and queues should prefer Redis instead of local container storage.
Running Background Processes
If your application uses queues, run queue workers as managed background processes:
bash
php phenix queue:workIf your queued workload uses a named queue, run the worker with the appropriate queue options documented in Queue.
If your application uses cron schedules, run the schedule worker:
bash
php phenix schedule:workKeep these processes separate from the main HTTP server process. In Docker-based deployments, run them as separate containers or dedicated managed processes.
Post-Deployment Tasks
After each release, review the following tasks:
- run
php phenix migratewhen schema changes are included - run
php phenix view:cachewhen the application renders views - restart the HTTP server if the new release updates runtime code
- restart queue workers if queued task code changed
- restart
schedule:workif schedule definitions changed - when using systemd, run
sudo systemctl restart phenix.serviceafter runtime code changes
Verification Checklist
- The application boots successfully with
APP_ENV=production APP_DEBUGis disabledAPP_DEBUG_LEVELis set to200or higherAPP_MODEmatches the real network topologyAPP_SERVER_MODEmatches the process model you startedAPP_TRUSTED_PROXIESis configured whenAPP_MODE=proxied- Database connectivity works with the configured
DB_*values - Redis connectivity works with the configured
REDIS_*values APP_KEYis present and valid- Queue workers are running when
QUEUE_DRIVERis used schedule:workis running when cron schedules are defined- Views are precompiled when production view caching is part of the release
- If systemd manages the process,
sudo systemctl status phenix.servicereports it as active
Common Pitfalls
- Using
php ./serverin production. That command is for development and hot reloading. - Setting
APP_MODE=proxiedwithout configuringAPP_TRUSTED_PROXIES. - Trusting overly broad proxy ranges instead of known proxy IPs or CIDRs.
- Using local or file-backed state in multi-instance deployments where Redis would be more appropriate.
- Running background workers inside the same lifecycle as the main HTTP process instead of managing them separately.
- Forgetting to run
php phenix migratewhen the release includes database changes. - Forgetting to run
php phenix view:cachefor applications that render views in production.