Custom Apps

How to Deploy a PHP App to a VPS Without a Dockerfile

Your composer.json and composer.lock are enough. Impreza builds the PHP image, serves the folder you name and never runs a Composer script during the build.

Most PHP projects do not need a container file. If your repository has a composer.json and a matching composer.lock, Impreza can build the image for you: PHP 8.4 with Apache, Composer 2, a non root runtime and the public folder you name. You pick the strategy php_composer and skip the Dockerfile entirely.

This recipe is deliberately narrow. Read the limits before you plan the migration, because they decide whether your project fits today or needs a Dockerfile.

What you get

  • PHP 8.4 and Apache built from your lockfile, with Composer 2 and an optimized autoloader
  • Your public folder served: php_document_root defaults to public, the folder holding index.php
  • A front controller fallback, so clean URLs reach index.php without an .htaccess file
  • Non root build and runtime on port 8080 by default
  • Optional strict health: the deploy only counts as successful when your path answers 2xx

What the build does, and what it refuses

The build installs exactly what your composer.lock pins, from public HTTPS archives, with plugins and scripts disabled. That is the security trade: no code from a dependency runs during the build, so a compromised package cannot execute in your image while it is being assembled.

The same choice creates the limits. The recipe refuses custom repositories, private credentials, asset build steps, database migrations and .htaccess overrides. If your project needs any of those, write a Dockerfile instead and deploy in dockerfile mode. That is a supported path, not a downgrade.

Two consequences worth planning for:

  • Composer scripts do not run. Frameworks that discover packages through a post install script do that work on the first request instead, writing to their own cache folder. Check your framework’s cache directory is writable by the app, which it is when it arrives with your source.
  • Migrations are yours to run. The deploy never touches your database. Run migrations from app inspection and maintenance or as a scheduled task after the release is live.

Deploy it

Check the two files

composer.json and composer.lock have to exist and match, in the folder you deploy. If your repository holds several apps, point project_dir at the one you want; that folder needs its own pair of files.

Name your public folder

php_document_root defaults to public. Set it to web, public_html or whatever holds your index.php. It accepts up to 120 characters and five path segments, and rejects hidden folders, parent segments, vendor, node_modules and symlinks. The value is saved when the deployment is created, and previews and redeploys inherit it.

Analyze before you deploy

Send your composer.json to the project analyzer, through your AI agent or the portal, and read what it reports back: framework hints, and findings that would block the build. The analysis is advisory, it does not fetch your repository and it does not authorize the deploy. Send configuration text only, never a .env file.

Create the deployment

From My Apps in Advanced view, the CLI, the REST API or your AI agent, create a custom deployment with build strategy php_composer, your Git URL or uploaded source, the document root and a target port if you want something other than 8080.

Add a health path

Give the app a route that answers 2xx when it is genuinely ready, such as /health, and set it as healthcheck_path. Turn on require_healthy_start to make that check decide whether the deploy succeeded. See recovering from a failed deploy for what happens when it does not answer.

The health check is stricter than you may expect

The probe runs against 127.0.0.1 on your target port and requires a 2xx status with no redirect. A PHP app that answers the root path with a 302 to a login screen will fail the check. Point healthcheck_path at a route that returns 200 on its own, and remember that require_healthy_start needs agent 0.6.3 or newer.

Private dependencies need a different route

The recipe installs from public archives only. A package from a private Packagist or a VCS repository is refused. Vendor the dependency, publish it to a public registry, or move to a Dockerfile build, where you control the credentials yourself.

What Apache is configured to do

Directory listings are off, symlinks are not followed, and override files are ignored. Requests that do not match a file fall back to index.php, which is what a front controller framework expects. Server tokens are trimmed so responses do not advertise the build.

After the first release

A release that builds is not a release that works. Open the app, exercise a route that touches the database, and read the runtime health of the containers, which is reported separately from the result of the last operation.

When you redeploy, the saved document root, health path and startup policy come along, so the second deploy is a single action. If a replacement fails, an eligible healthy previous release can be recovered, and your volumes are preserved. That is recovery of the application, not of your data: plan database changes with app backups before you ship a migration.

Start now

Get an offshore VPS with the agent preselected, read the build recipe reference at docs.imprezahost.com, or compare with the other no Dockerfile recipes: Node, Python and static sites.

Ready to build privacy-first?

No KYC, no email required, crypto payment. Deploy an offshore server in minutes, or do it all by chat with the Impreza agent.