Skip to content

Build and Deploy Your Winter Boot PHP 8 Application

Winter Boot applications can be shipped in several ways depending on your infrastructure: as a self-contained Phar archive for bare-metal hosts, as a Docker image for containerised deployments, or via standard PHP-FPM for environments that already manage PHP processes. The framework ships with a sample init.d script and Docker base image to get you started quickly. A fully working example, including a Dockerfile and a box.json build manifest, is available in the suvera/winter-example-service repository.

Before building, choose the runner that matches your deployment target. Each runner starts the application in a different mode.

WinterWebSwooleApplication

Recommended for production. Starts a Swoole HTTP server with multiple worker processes. Supports #[Async], #[Scheduled], daemon threads, and the local KV/Queue stores. Requires the swoole PHP extension.

WinterWebApplication

Traditional PHP-FPM / CGI runner. Each request is a new process invocation — no persistent workers. Use this when Swoole is unavailable or when integrating with an existing PHP-FPM pool.

WinterCliApplication

Command-line runner. Boots the application context and fires the ApplicationReady event, then exits. Useful for batch jobs, migrations, and one-off administrative scripts.

bin/app.php
// Swoole HTTP server (recommended for production)
(new WinterWebSwooleApplication())->run(MyApplication::class);
// PHP-FPM / traditional
(new WinterWebApplication())->run(MyApplication::class);
// CLI / batch
(new WinterCliApplication())->run(MyApplication::class);

Use Box to compile your application and all its Composer dependencies into a single, self-contained Phar archive.

  1. Install dependencies

    Terminal window
    composer install --no-dev --optimize-autoloader
  2. Install Box

    Terminal window
    composer global require humbug/box

    Verify the installation:

    Terminal window
    box --version
  3. Create box.json

    Add a box.json configuration file to your project root:

    box.json
    {
    "output": "target/my-app.phar",
    "main": "bin/app.php",
    "directories": ["src", "config"],
    "compression": "GZ",
    "chmod": "0755"
    }
  4. Compile the Phar

    Terminal window
    box compile

    On success, the output file is a standalone executable:

    Terminal window
    php target/my-app.phar -c /etc/my-app/config

Winter Boot ships a base Dockerfile at build/docker/Dockerfile that extends the official php:8.5-cli image and pre-installs the extensions most modules require:

build/docker/Dockerfile
#####################################################################################
# WinterBoot PHP Image - Run below command
#
# docker build . -t suvera/winter-boot:latest -f ./build/docker/Dockerfile
#
#####################################################################################
FROM php:8.5-cli
RUN apt-get update \
&& apt-get install -y librdkafka-dev libzip-dev procps libssl-dev libcurl4-openssl-dev \
&& pecl install redis \
&& pecl install rdkafka \
&& pecl install swoole-6.2.2 \
&& pecl install zip \
&& docker-php-ext-enable redis rdkafka swoole zip

Build your application image on top of the base image:

Dockerfile
FROM suvera/winter-boot:latest
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
COPY src/ src/
COPY config/ config/
EXPOSE 8080
CMD ["php", "src/MyApplication.php", "-c", "config/"]
Build
docker build -t my-winter-service:latest .
Run
docker run -p 8080:8080 my-winter-service:latest
PHP 8.4+ required

Winter Boot requires PHP 8.4 or higher. The base image ships PHP 8.5-cli.

Swoole extension

Install via pecl install swoole for the WinterWebSwooleApplication runner. The base image already includes it.

Composer install at build time

Run composer install --no-dev --optimize-autoloader during the docker build step, not at container start. Installing at start adds latency to every container launch.

Config directory at runtime

Mount your application.yml at a known path and pass it with -c /config at container start. Avoid baking environment-specific config into the image layer.

Kubernetes — Init Container for Migrations

Section titled “Kubernetes — Init Container for Migrations”

When deploying with SQL migrations, run the migrator as a Kubernetes init container so the schema is up-to-date before any application pod starts:

k8s/deployment.yaml
initContainers:
- name: db-migrate
image: my-winter-service:latest
command: ["php", "target/winter-migrations-app.phar", "-c", "/config"]
volumeMounts:
- name: config
mountPath: /config

For traditional host-based deployments, build a Phar archive and manage the process with the provided init.d sample script.

Start the application with an explicit config directory:

Terminal window
php target/my-app.phar -c /etc/my-app/config/

The -c flag points to the directory containing your application.yml (and any additional config files such as logger.yml).

Winter Boot ships a ready-to-use init.d template at build/init.d.sample.sh. Copy it and register it with your init system:

Terminal window
cp build/init.d.sample.sh /etc/init.d/my-winter-service
chmod +x /etc/init.d/my-winter-service

Edit the variables at the top of the file to match your environment:

/etc/init.d/my-winter-service
USER=www-data
SERVICE_NAME=my-winter-service
SERVICE="target/my-app.phar"
CONFIG_DIR="/etc/my-app/config"
ADMIN_PORT="9090"
ADMIN_TOKEN_FILE="/etc/my-app/admin.token"
LOG_FILE="/var/log/my-winter-service/app.log"
PID_FILE="/var/run/my-winter-service.pid"
PHP_BINARY="php"

Then control the service with standard commands:

Terminal window
service my-winter-service start
service my-winter-service stop
service my-winter-service restart
service my-winter-service status

To use systemd instead, create a native unit file:

/etc/systemd/system/my-winter-service.service
[Unit]
Description=My Winter Boot Service
After=network.target
[Service]
Type=simple
User=www-data
ExecStart=/usr/bin/php /var/www/my-app/target/my-app.phar -c /etc/my-app/config
Restart=on-failure
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

Reload systemd and enable the service:

Terminal window
systemctl daemon-reload
systemctl enable my-winter-service
systemctl start my-winter-service

When using WinterWebApplication (without Swoole), point your web server’s FastCGI configuration at your application entry point:

/etc/nginx/sites-available/my-app.conf
location / {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME /var/www/my-app/bin/app.php;
include fastcgi_params;
}

The suvera/winter-example-service repository demonstrates a complete Winter Boot microservice with a production-ready setup:

Dockerfile

A production-ready multi-stage Dockerfile built on the Winter Boot base image.

box.json manifest

A box.json build manifest for Phar packaging, suitable for CI/CD pipelines.

Migration init container

SQL migration setup using the Kubernetes init container pattern.