DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
VGSources
Blog

How to Deploy a Composer-Based WordPress Site (Bedrock, Git, and Server Setup)

A practical Bedrock deployment guide covering Composer installs, Git hygiene, release directories, web-server roots, secrets, uploads, managed-host mappings, CI/CD and rollback.
Length10 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The reliable way to deploy a Composer-based WordPress site is to deploy the repository as an immutable release, run composer install --no-dev --optimize-autoloader on the server or in CI for every release, keep .env and uploads outside the repository, and point the web server at the project’s web/ directory. Do not point the server at the repository root: that exposes files that should never be public and bypasses Bedrock’s intended layout.

This guide uses Roots Bedrock as the reference implementation, but the deployment principles also apply to other Composer-managed WordPress projects.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What a Composer-based WordPress deployment contains

In a conventional WordPress installation, core files, plugins, themes and uploads commonly sit together under one public directory. A Composer-based project separates those concerns. Composer resolves the versions declared in composer.json, records the exact dependency graph in composer.lock, and installs packages into vendor/ and the Bedrock web tree.

Bedrock places the public entry point in web/. WordPress core is installed below web/wp/, while content such as plugins, themes and uploads is under web/app/. The repository root contains configuration and dependency metadata, not files that should be served directly.

The deployment consequence is important: the server needs a build step, a persistent location for environment data and user uploads, and a document root that ends at web/. Copying only tracked files and skipping Composer leaves the application incomplete; running Composer only once and reusing an old vendor/ directory can leave code out of sync with the lock file.

The current Bedrock documentation lists PHP 8.3 or newer and Composer as requirements. Older tutorials cite lower PHP versions, so verify the current requirements before provisioning a server: Roots Bedrock installation documentation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Preflight requirements

Requirement What to verify Why it matters
PHP PHP 8.3 or newer, with the extensions required by your selected plugins and theme The current Bedrock documentation sets the supported floor. A host running an older PHP release will fail before WordPress loads.
Composer Composer is installed and available to the deploy user Dependencies must be resolved from composer.lock on every release.
Git or artifact delivery The server or CI system can fetch the exact commit being released Reproducible releases require a known revision, not a mutable working tree.
Document-root control You can set Nginx root or Apache DocumentRoot to web/ Only the intended public tree should be reachable over HTTP.
Persistent storage A writable, backed-up location for web/app/uploads Media must survive code releases and rollbacks.
Secret storage A server-side .env, secret manager, or equivalent Database credentials and salts must not be committed to Git.

Run a preflight check as the same user that will deploy:

php -v
composer --version
git --version

Confirm the PHP version reported by the command-line binary matches the PHP version used by the web server. It is common for a host to expose one PHP binary in SSH and another through PHP-FPM or Apache.

Prepare the repository correctly

Commit the dependency manifests

Commit both composer.json and composer.lock. The first declares the allowed packages and version constraints; the lock file records the exact versions and transitive dependencies to install. Deploying without the lock file allows dependency resolution to change between releases.

Keep generated and private data out of Git

The repository should contain application code and configuration templates, not generated dependencies, core copies, media or secrets. At minimum, exclude:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • vendor/, because Composer creates it during deployment.
  • web/wp/, because Bedrock obtains WordPress core as a Composer dependency.
  • web/app/uploads/, because uploads are persistent runtime data rather than release code.
  • .env, because it contains environment-specific secrets.

The exact ignore patterns depend on your project’s directory names, but the rule is consistent: track the manifests and source, regenerate dependencies, and mount runtime data separately. Review the resulting archive or checkout before the first release so a forgotten local .env or media directory cannot be published.

Handle private packages deliberately

Commercial or private plugins often cannot be downloaded from the public package registry. Configure Composer authentication for a private repository or an internal package proxy, and store its credentials in the deploy system’s secret store. Do not put repository tokens in composer.json, shell history or the Git repository. A deployment should fail clearly when a private package is unavailable rather than silently substituting a different version.

Use a release layout instead of deploying in place

An atomic release layout lets you install dependencies without interrupting the currently running site. A typical structure is:

/var/www/example/
├── current -> releases/2026-10-14-1200/
├── releases/
│   ├── 2026-10-14-1100/
│   └── 2026-10-14-1200/
└── shared/
    ├── .env
    └── uploads/

Each release directory contains one Git checkout and its generated vendor/ tree. The current symlink is the only path referenced by the web server. The shared directory contains data that must persist across releases. Keep old releases until the new one has passed its smoke tests, then remove them according to your disk-retention policy.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Deploy a release step by step

  1. Fetch a clean checkout. Create a new release directory and check out the exact commit or tag you intend to publish. Do not deploy from a developer’s uncommitted working tree.
  2. Install production dependencies. From the release directory, run:
    composer install --no-dev --optimize-autoloader

    The --no-dev option omits development-only packages, while --optimize-autoloader prepares the class loader for production. Roots’ deployment guidance expects Composer installation to run as part of every deployment, not just during initial setup: WordPress with Composer and Git.

  3. Link the environment file. Create a symlink from the release to the protected shared file:
    ln -sfn /var/www/example/shared/.env /var/www/example/releases/2026-10-14-1200/.env

    Set ownership and permissions so the deploy process can read the file but it is not world-readable. Never place the shared directory below the web server’s document root.

  4. Link persistent uploads. If uploads are stored in the shared directory, link them into the release at the path expected by the application:
    mkdir -p /var/www/example/shared/uploads
    ln -sfn /var/www/example/shared/uploads /var/www/example/releases/2026-10-14-1200/web/app/uploads

    Use a copy or synchronization step instead if your host supplies object storage. The essential property is persistence independent of the release directory.

  5. Build front-end assets when required. If the theme includes a JavaScript or CSS build, install the project’s declared Node dependencies and run its production build in the theme directory, commonly with npm run build. Do this before switching traffic to the release; otherwise the site can load PHP successfully while serving missing or stale assets. Use the Node version specified by the project rather than assuming the host’s default.
  6. Apply filesystem permissions. The web user needs read access to release code and write access only to directories that genuinely require it, such as uploads, caches or generated logs. Avoid making the entire release tree writable by PHP.
  7. Run database migrations or update tasks, if your project has them. Perform these as an explicit, reviewed release step. Do not assume that a plugin activation or schema change is safe to run automatically on every web request.
  8. Switch the current symlink atomically.
    ln -sfn /var/www/example/releases/2026-10-14-1200 /var/www/example/current

    Because the web server follows current, the new code becomes active as one filesystem operation. Keep the previous symlink target available for rollback.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  9. Reload services only when configuration changed. A PHP-FPM reload may be appropriate after changing PHP configuration or opcache settings; a normal code release does not require restarting the web server. Follow your host’s service-management policy.
  10. Run smoke tests. Request the home page, an ordinary post, the login screen, a representative image from uploads and one page that exercises a key plugin. Check the PHP error log, HTTP status codes and browser console before declaring the release successful.

Point Nginx or Apache at web/

Nginx

Set the virtual host’s root to the absolute path ending in /current/web. A representative configuration is:

server {
    server_name example.com;
    root /var/www/example/current/web;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ .php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php-fpm.sock;
    }

    location ~* ^/app/uploads/.*.php$ {
        deny all;
    }
}

Adapt the PHP-FPM socket, TLS settings and other host-specific directives to your system. The important Bedrock-specific details are the web root, front-controller fallback and explicit blocking of PHP execution in uploads. Roots documents the required server arrangement and security considerations here: Bedrock server configuration.

Apache

Set DocumentRoot to /var/www/example/current/web, enable the rewrite module, and allow the Bedrock-provided rules to handle front-controller requests. Deny PHP execution beneath web/app/uploads using your Apache version’s directory or location rules. Verify that the virtual host does not expose the parent project directory through an alias, directory listing or fallback site.

Hosts without document-root control

Some shared hosts force the document root to the account’s project directory. The documented workaround is an .htaccess file at the project root that forwards requests into web/: Roots’ server-configuration guidance. This is less desirable than changing the real document root because the repository root remains adjacent to the public path, so confirm that the host’s rewrite and access rules deny direct reads of .env, composer.json, composer.lock and other private files.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Configure environment data and uploads

Keep .env server-side

Populate the shared .env with the production database connection, site URL, salts and any plugin-specific credentials required by the project. Store it outside the public tree, restrict its permissions, and provide a separate file for staging. A symlink from each release to the shared file avoids copying secrets into every release directory.

Protect uploads

Uploads are user-controlled content. They should be writable by the application only where necessary, served as media, and never treated as a place to execute PHP. Back them up independently of code releases. If you restore a database backup without the matching uploads snapshot, attachment records can point to missing files.

Use production defaults

Bedrock’s production defaults are intended to prevent editing core and plugins from the WordPress dashboard. Keep code changes in Git and deploy them through the release process. Enable dashboard updates only in a controlled development environment, then commit and release the resulting dependency changes.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Managed hosting has extra mapping rules

Not every WordPress host accepts a custom web/ document root or lets you run Composer. Before committing to a host, verify three capabilities:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Composer can run during deployment, either on the host or in a CI-built artifact.
  • The web root can be set to web/, or the provider documents a safe equivalent.
  • Persistent uploads and environment variables can be mounted outside each release.

Some managed platforms require a file-restructuring step. For example, WP Engine’s Bedrock deployment guidance maps web/app/ to the platform’s expected wp-content location rather than accepting the Bedrock tree unchanged: WP Engine’s Composer-based deployment guide. Treat that as a platform-specific adapter, not a reason to change the repository for every host.

Budget shared hosting may lack SSH access, Composer or configurable document roots. In that case, build the release in CI, deliver the generated artifact including vendor/, and follow the provider’s documented public-directory mapping. Do not upload a developer’s entire working directory or expose the repository root merely because the host cannot run Composer.

Automate with CI/CD without losing reproducibility

A deployment service can perform the same release sequence consistently. A tool such as DeployHQ documents a Bedrock workflow based on fetching the repository, installing Composer dependencies and activating a release: DeployHQ’s Bedrock deployment guide. GitHub Actions or another CI system can implement the sequence directly.

Keep the pipeline stages explicit:

  1. Check out the requested commit.
  2. Validate composer.lock and run composer install --no-dev --optimize-autoloader.
  3. Run tests, linting and the theme’s production asset build.
  4. Package or publish the release to a new server directory.
  5. Attach the shared .env and uploads.
  6. Run smoke tests against the release before switching traffic.
  7. Move the current symlink and record the deployed commit.

Cache Composer downloads in CI if desired, but do not treat a cache as the deployed dependency tree. The release must contain the dependencies produced from the checked-in lock file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rollback and recovery

If smoke tests or monitoring show a regression, first switch current back to the previous known-good release. This restores application code without deleting the failed release, making log inspection possible. If the release included a database migration, code rollback alone may be unsafe; use a migration strategy that supports backward compatibility or restore the database from a tested backup.

Common recovery cases include:

  • Composer failure: inspect the first package-resolution or authentication error, confirm the server PHP version, and verify private repository credentials. Do not bypass the lock file to make a single deployment pass.
  • HTTP 404 for every route: check that the document root ends in web, URL rewriting is enabled, and Nginx or Apache is forwarding unknown paths to index.php.
  • “Error establishing a database connection”: verify the shared .env symlink, file permissions and database host credentials for the active release.
  • Missing images after deployment: confirm that web/app/uploads points to the persistent storage location and that the web user can read it.
  • Assets return 404 or old JavaScript: inspect the theme build output, ensure the build ran in the release directory and clear only the relevant cache after confirming the files exist.
  • Uploads execute as PHP: add or correct the web-server deny rule immediately, then inspect the upload tree for unexpected files.

Final deployment checklist

  • PHP 8.3 or newer is available to both CLI and web execution.
  • composer.json and composer.lock are committed at the release commit.
  • vendor/, web/wp/, uploads and .env are excluded from Git.
  • composer install --no-dev --optimize-autoloader ran for this release.
  • The document root is web/, not the repository root.
  • The shared .env and uploads are attached to the active release.
  • PHP execution is blocked in uploads.
  • Theme assets were built when the project requires a Node build.
  • Smoke tests cover front-end pages, login, uploads and critical plugins.
  • The previous release and a tested database backup are available for rollback.

For a broader explanation of Bedrock’s Composer-and-Git model, see the Bedrock deployment guide. The repeatable pattern remains the same: build from the lock file, keep runtime state out of releases, expose only web/, and switch complete releases atomically.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.