ForgeDeployment

The tool your app uses to get itself onto a real server — provision a machine, push your code, wire up a database, and keep it updated. A set of CLI commands, not framework machinery.

Overview

ForgeDeployment arranges the whole journey from "working in a folder" to "running in the cloud". It talks directly to a cloud provider to create a server, connects over SSH to install PHP, a database, and Nginx, then uploads your project and configures the site.

After the first full deploy, you don't repeat the heavy lifting. You push updated files with an incremental command, roll back to a previous commit if something breaks, and check the current state at any time.

Framing: this is the tooling you run from your own project to ship it. There's no "framework" layer to learn — a few commands, a config file, and your provider credentials.

Installation

php forge.php package:install-module --module=forge-deployment

Two environment variables hold the credentials for the providers you use:

Variable Used for
FORGE_DEPLOYMENT_DIGITALOCEAN_API_TOKEN Creating and managing servers on DigitalOcean
FORGE_DEPLOYMENT_CLOUDFLARE_API_TOKEN Configuring DNS records on Cloudflare

Configuration

Deployment settings live in a PHP file at your project root. Generate a starter one with:

php forge.php modules:forge-deployment:init

This creates forge-deployment.php (the loader also accepts deployment.php), returning an array with three sections:

Section What it describes
server Name, region, size, OS image, and SSH key path for the new machine
provision PHP version, database type/version, and the app database name/user/password
deployment Domain, SSL email, commands to run, post-deployment commands, and extra env vars

The post-deployment commands are the useful part — run on the server after files land, each through your PHP binary. A typical set from the generated template:

'post_deployment_commands' => [
    'cache:flush',
    'cache:warm',
    'db:migrate --type=all',
    'storage:link',
    'modules:forge-deployment:fix-permissions',
    'asset:link --type=module --module=forge-wire',
],

Any command starting with php forge.php has its php swapped for the provisioned version (e.g. php8.4); plain strings are run as {php} forge.php {command}.

Commands

All commands are prefixed with modules:forge-deployment: and most are wizard-driven — run them bare to be prompted, or pass options to skip the questions.

Command Purpose
...:init Generate the forge-deployment.php config file
...:deploy Full wizard-driven deploy (--provider, --ssh-key, --config)
...:create-server Create a VPS on the provider (--provider, --ssh-key)
...:delete-server Delete the deployed server and related resources
...:provision Provision an existing server (--host, --ssh-key, --ram)
...:deploy-app Upload your app to a provisioned server (--host, --domain, --php-version)
...:deploy-env Deploy a merged .env to the server
...:update Push changed files to an existing server (--skip-commands, --working-tree, --force-full)
...:rollback Revert to the previous commit (--skip-commands, --skip-confirmation)
...:resume Continue a failed deployment from its last checkpoint
...:status Show the current deployment state and completed steps
...:reset Clear the saved deployment state to start fresh (--force)
...:setup-ssl Issue a Let's Encrypt certificate (--host, --domain, --php-version)
...:configure-dns Add the A record via Cloudflare (--domain, --ip)
...:fix-permissions Fix ownership and permissions in the current project root

The Deploy Flow

A full deploy walks through a fixed set of steps, saving each one as it completes:

  1. Connect over SSH with your key.
  2. Provision the system — swap, firewall, updates, and kernel tuning.
  3. Install and configure PHP from your config's version.
  4. Install your database and create the app's database and user.
  5. Install and configure Nginx.
  6. Upload the project (zipped, sent over SSH, unpacked) into /var/www/<domain>.
  7. Write the Nginx site configuration.

Each finished step is written to a state file, .forge-deployment-state.json at your project root. It records the server IP and ID, the domain, which steps are done, and — once you ship an update — the last deployed commit. That state is what lets resume pick up a failed deploy right where it stopped and update know exactly what changed since last time.

Scope guard: the source tree is filtered by a .forgeignore file at your project root (gitignore-style — ! to un-ignore, /* or trailing / for directories, * and ? wildcards, # for comments). Keep secrets and heavy assets out of what gets shipped.

Incremental Updates

Once your app is live, update deploys just what changed. It compares commits through git, uploads only the changed files, then runs your post-deployment commands:

php forge.php modules:forge-deployment:update
php forge.php modules:forge-deployment:update --force-full   # full re-sync
php forge.php modules:forge-deployment:update --working-tree # diff against uncommitted changes

--skip-commands uploads files but skips the post-deployment commands, handy for a quick asset push.

If a deploy goes wrong, rollback restores the previously deployed commit's files and re-runs commands (or skips them with --skip-commands). Both commands verify the server is reachable first and refuse to run if it isn't.

Note: incremental updates rely on a git repository. If your project isn't a git working tree, use deploy or update --force-full to push the whole tree.

Environment & Secrets

Your deployment.env_vars config entries are merged on top of your local .env (or env-example if .env is missing) and written to the server as its .env. Database settings from the config are folded in automatically, so the remote app points at the database just provisioned:

'env_vars' => [
    'APP_ENV' => 'production',
    'APP_DEBUG' => 'false',
    'CENTRAL_DOMAIN' => 'domain.com',
    'CORS_ALLOWED_ORIGINS' => ['https://domain.com'],
],

If there's no local .env, the deployment generates an application key on the server for you. Use deploy-env at any time to push a freshly merged .env without re-uploading the whole app.

Keep it out of the archive: your real secrets belong in the server's .env, not in files that ride along in the deployment zip. Add anything sensitive to .forgeignore.

Cloud Providers & SSL

DigitalOcean ships as the built-in provider for creating and managing servers — droplets, SSH keys, regions, sizes, and images. The provider system is a small contract (createServer, status, delete, listing helpers), so other providers can be added the same way.

Two supporting pieces round out a real deploy:

  • SSL — setup-ssl issues a Let's Encrypt certificate for your domain and points Nginx at it.
  • DNS — configure-dns creates the A record through Cloudflare pointing your domain at the server IP.

When you work through the Hub, deployment history is kept under storage/framework/deployments/, and sensitive config values (tokens, passwords, secrets) are masked so they never show up in logs.