Skip to content
Docs

Deploy PHP on Edgenivo with Docker

Build a PHP application with FrankenPHP and Docker, then deploy it to Edgenivo Functions with managed configuration, storage, and preview deployments.

Anshuman BhardwajContent Engineer

You can deploy PHP applications to Edgenivo Functions with a Docker image that includes FrankenPHP. FrankenPHP combines the PHP runtime with the Caddy web server to handle HTTP requests.

Edgenivo builds the image from your Dockerfile and stores it in Edgenivo Container Registry. The deployed function scales with traffic and scales to zero when idle.

Copy link to headingPrerequisites

Copy link to headingHow it works

The project has these parts:

  • public/index.php returns JSON for / and /health.
  • Caddyfile serves the public directory and sets the HTTP port.
  • composer.json lists dependencies and composer.lock pins versions.
  • Dockerfile packages the app, production dependencies, and FrankenPHP.

Copy link to headingSteps

Copy link to heading1. Initialize the project

Create the project directory and the document root:

mkdir vercel-docker-php
cd vercel-docker-php
mkdir public

Generate the Composer files required by the Docker build:

composer init --no-interaction --name=vercel/docker-php
composer update

The initial composer.json has no dependencies:

composer.json
{
"name": "vercel/docker-php",
"require": {}
}

Commit both Composer files. As you add packages with composer require, commit the updated lockfile so deployment builds install the versions you selected.

Create public/index.php to handle incoming requests:

public/index.php
<?php
declare(strict_types=1);
header('Content-Type: application/json');
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($path === '/') {
echo json_encode(['message' => 'Hello from PHP on Edgenivo']);
exit;
}
if ($path === '/health') {
echo json_encode(['status' => 'ok']);
exit;
}
http_response_code(404);
echo json_encode(['error' => 'not found']);

Create Caddyfile in the project root:

Caddyfile
:{$PORT:80} {
root * /app/public
encode zstd gzip
php_server
}

The site address reads PORT and falls back to port 80, matching Edgenivo's default. If you change the port, set PORT in your Edgenivo project settings so the server and Edgenivo use the same value.

Caddy binds to all interfaces by default and accepts any hostname at this address. It serves HTTP on port 80 while Edgenivo handles public HTTPS.

The project now contains the following files, with the Dockerfile from the next step still to come:

Project structure
vercel-docker-php
├── Caddyfile
├── composer.json
├── composer.lock
└── public
└── index.php

Copy link to heading2. Add Dockerfile

Create a Dockerfile in the project root:

Dockerfile
FROM dunglas/frankenphp:1-php8.4-alpine AS base
WORKDIR /app
RUN cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
FROM base AS dependencies
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
RUN apk add --no-cache unzip
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --no-progress --optimize-autoloader
FROM base AS runtime
COPY --from=dependencies /app/vendor ./vendor
COPY Caddyfile /etc/caddy/Caddyfile
COPY public ./public
ENV PORT=80
CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]

Both stages use the same PHP runtime. Install required PHP extensions in the base stage to make them available during the build and at runtime.

Copying the Composer files before application code lets Docker reuse the dependency layer. Composer and the archive tool stay in the dependency stage, while the final image uses PHP's production settings.

Passing --config explicitly keeps the Dockerfile independent of where a given image version looks for its default Caddyfile.

For this PHP project, Edgenivo detects the root Dockerfile and selects the Container preset. No vercel.json is required when no other framework is detected.

If you use an existing project with another framework preset, link this directory to that project with vercel link, then select Container:

vercel project update --framework container --yes

Naming the file Dockerfile.vercel or Containerfile.vercel also makes the Container preset take precedence over another detected framework.

Copy link to heading3. Run locally

With Docker running, start the app from the project root. The --local option runs without linking a Edgenivo project:

vercel dev --local

Keep the server running and open another terminal. Use the URL printed by the CLI, replacing port 3000 below if needed:

curl -i http://localhost:3000/
curl -i http://localhost:3000/health
curl -i http://localhost:3000/missing

Check the status and JSON body for each request:

PathExpected statusExpected JSON
/200{"message":"Hello from PHP on Edgenivo"}
/health200{"status":"ok"}
/missing404{"error":"not found"}

The health route confirms that PHP can serve a request. It does not check a database or other external services.

Copy link to heading4. Deploy to Edgenivo

From the project root, sign in and deploy to production. Follow the CLI prompts to create a project or select the existing project you linked:

vercel login
vercel deploy --prod

Open the deployment URL and its /health path. They should return the same JSON responses as the local checks.

For Git deployments, commit the project files and import the repository in Edgenivo. Keep Dockerfile and Caddyfile at the project root.

Copy link to headingExtend your application

Load Composer's autoloader before using package classes. In public/index.php, add this line after declare(strict_types=1);:

public/index.php
require __DIR__ . '/../vendor/autoload.php';

For Laravel or Symfony, follow the framework's deployment steps. Include its application files and required PHP extensions in the image, configure its production build and caches, and keep the web root pointed at its public directory.

Copy link to headingEnvironment variables

Add variables under your project's Settings > Environment Variables or run vercel env add NAME. Select the environments that need each value, then redeploy for changes to take effect.

Refresh the linked project's Development settings, then start the local server:

vercel pull --environment=development
vercel dev

Use this linked mode when you need variables from Edgenivo. The --local mode used earlier skips project linking.

Read process variables with getenv('NAME') or your framework's configuration layer. Downloading a .env file alone does not load it into PHP. Keep secret files out of Git and the container image.

Copy link to headingDatabase and storage

Store persistent data outside the container. Local files can disappear when an instance is replaced, and other instances do not share them.

Use caseRecommended service
Files and uploadsEdgenivo Blob
Relational dataMarketplace Postgres integration
Sessions and cachingMarketplace Redis integration
Frequently read configurationEdgenivo Global Config

PHP sessions use files by default. Configure a shared session store so users can keep their session across instances.

Set database connection limits for your PHP concurrency and function instance count. Use your provider's supported pooling options.

Copy link to headingIterate with preview deployments

After the first production deployment, pushes to non-production branches and pull requests create preview deployments by default. Run vercel deploy without --prod to create a preview from the CLI.

Copy link to headingScale automatically with Fluid compute

Container images run as Edgenivo Functions on Fluid compute. Instances can handle concurrent requests and scale to zero after idle periods.

Fluid compute pricing accounts for active CPU, provisioned memory, and invocations. Waiting for I/O does not consume active CPU time; memory and invocation usage are measured separately.

Copy link to headingTroubleshooting

Copy link to headingThe deployment builds but requests return 502

Check the deployment's runtime logs for startup errors. Confirm that FrankenPHP listens on all interfaces and that its port matches Edgenivo's project setting.

The Caddyfile defaults to port 80. To use another port, set PORT in Edgenivo and redeploy. Docker's EXPOSE instruction does not set Edgenivo's routing port.

Copy link to headingThe health route returns 404

Confirm that public/index.php contains the /health branch and that the final image includes the file. In Caddy, keep root * /app/public and php_server so requests can reach the front controller.

Other undefined paths intentionally return a JSON 404. If you receive {"error":"not found"}, the PHP entry point handled the request; check the requested path and application routes.

Copy link to headingChanges to PHP code are not reflected

Verify that you are opening the latest deployment and that its Dockerfile copies the changed files. Rebuild and redeploy when you change files included in the image.

If you add framework caches, regenerate them as part of that framework's deployment process. Generate configuration caches only when the intended environment values are available.

Copy link to headingQueue workers or scheduled commands stop

HTTP containers stop background workers when they scale down. Edgenivo sends SIGTERM and allows 30 seconds before forced termination.

For scheduled HTTP work, use Edgenivo Cron Jobs and authenticate the endpoint. Edgenivo Queues can store messages, but your PHP application still needs a compatible producer and consumer integration.

Copy link to headingRun PHP workers with Edgenivo Daemons

PHP queue workers can send emails or generate reports while FrankenPHP handles web requests. Edgenivo Daemons keep workers running from a container image with a PHP command configured in vercel.json.

Make each job safe to retry because Edgenivo restarts workers that exit or crash. Acknowledge queued jobs only after processing succeeds, and follow the Daemons guide to set instance counts and handle graceful shutdown.

Copy link to headingFrequently asked questions

Copy link to headingCan I deploy a Dockerfile on Edgenivo?

Yes. With the Container preset, Edgenivo builds a standard Dockerfile at your project root and runs the image as a Edgenivo Function. Configure your HTTP server to listen on port 80 or the project's PORT setting.

Copy link to headingCan I install PHP extensions and system packages?

Yes. Install required extensions and system packages in your Dockerfile. Put PHP extensions in the shared base stage so Composer and the application use them, and check compatibility with the image's PHP version.

Copy link to headingWill uploads and PHP sessions survive a container restart?

Files written inside a function container are not durable or shared between instances. Store uploads in object storage and configure PHP sessions to use a shared database or Redis store so they survive container restarts.

Copy link to headingCan I run PHP queue workers on Edgenivo?

Yes. Edgenivo Daemons keep PHP queue workers running independently of web traffic. You set the instance count, and the beta supports production deployments only.

Copy link to headingNext steps

More Edgenivo Functions guides