Shipping It

Package the module with Composer, deploy it to a site in production mode, and know what to watch after.

Syntaxbin/magento deploy:mode:set production

Shipping is two jobs: making the module installable, and applying it to a site running in production mode.

Packaging

A module with a composer.json of type magento2-module installs into vendor/ instead of app/code. The autoload.files entry pointing at registration.php is what makes Magento see it at all. Tag a version so a site can pin one.

Production mode

Production mode never generates code during a request. Factories, proxies and interceptors must exist beforehand, which is what setup:di:compile writes, and static view files must be in pub/static, which is what setup:static-content:deploy writes. bin/magento deploy:mode:set production runs both as part of the switch.

The deploy

Order matters. Maintenance on, install the code, setup:upgrade, setup:di:compile, setup:static-content:deploy, cache:flush, maintenance off. Compile after upgrade, because setup:upgrade clears generated code unless given --keep-generated. Reindex if the module added an indexer, and restart queue consumers if it added one, since a running consumer holds the old code.

Afterwards

Watch var/log/exception.log and var/report/ for the first few minutes. A missing generated class surfaces there as a fatal error rather than in the browser. Confirm bin/magento setup:db:status reports everything up to date, and open the new admin grid with a non-administrator role to prove the ACL resource exists.

The CLI accepts abbreviations: bin/magento s:d:c is setup:di:compile. Use the full names in deploy scripts.

Example

Example · json
{
    "name": "acme/module-locator",
    "description": "Pickup locations with an admin grid, a storefront page, REST and GraphQL",
    "type": "magento2-module",
    "license": "proprietary",
    "version": "1.0.0",
    "require": {
        "php": "~8.4.0",
        "magento/module-ui": "*",
        "magento/module-backend": "*",
        "magento/module-webapi": "*",
        "magento/module-graph-ql": "*"
    },
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Acme\\Locator\\": ""
        }
    }
}

When to use it

  • Hand a module to a client as a Composer package with a pinned version instead of a zip of app/code.
  • Write the deploy runbook for a release that adds a table and a new admin screen.
  • Diagnose a module that works locally but 500s in production after a release.

More examples

Installing it on a production host

This is the whole deploy for one module on a single host. maintenance:enable serves the 503 page while the schema changes, which matters because setup:upgrade may alter a table. The compile step comes after setup:upgrade, since setup:upgrade removes generated code unless you pass --keep-generated. On a multi-server setup the same commands run once on a build machine and the result is shipped, which is what the pipeline lessons cover. If this is the deploy that also switches the site over, bin/magento deploy:mode:set production replaces the compile and static-content steps because it performs both.

Example · bash
# from the Magento root, as the file-system owner
bin/magento maintenance:enable

composer require acme/module-locator:1.0.0
bin/magento module:enable Acme_Locator

bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy en_US

bin/magento cache:flush
bin/magento maintenance:disable

# bin/magento setup:di:compile prints, in order:
# Compilation was started.
# ...
# Generated code and dependency injection configuration successfully.

Confirming the deploy actually took

Four checks, each catching a different failure. module:status catches a module that was packaged but never enabled on this host. setup:db:status catches a schema or patch that did not run, which is the usual cause of "column not found" after a release. cache:status catches a cache type someone disabled while debugging and never re-enabled, which is slow rather than broken and therefore easy to miss. The log tail is where a missing generated class or an unresolved dependency actually appears; var/report holds one file per uncaught exception, named with the id shown on the error page.

Example · bash
bin/magento module:status Acme_Locator
# Module is enabled

bin/magento setup:db:status
# All modules are up to date.

bin/magento indexer:status
# Catalog Search                           Ready
# ...

bin/magento cache:status
# config: 1
# layout: 1
# full_page: 1
# ...

tail -n 100 var/log/exception.log var/report/* 2>/dev/null

How the module's enabled state travels to production

bin/magento module:enable does not talk to the database; it rewrites this array. Committing the change is how a production host learns the module should be on, which means the module:enable step can be dropped from the deploy entirely if the file is already correct in the repository. A 0 here is a module that is installed but off. Keep app/etc/config.php in version control and app/etc/env.php out of it: env.php holds the database credentials and the crypt key for that one environment.

Example · php
<?php
// app/etc/config.php - committed to version control, trimmed to the relevant part
return [
    'modules' => [
        'Magento_Store' => 1,
        'Magento_Ui' => 1,
        'Acme_Locator' => 1,
    ],
    // 'scopes', 'system', 'themes' and other dumped configuration follow
];

Discussion

  • Be the first to comment on this lesson.