Shipping It
Package the module with Composer, deploy it to a site in production mode, and know what to watch after.
bin/magento deploy:mode:set productionShipping 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
{
"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.
# 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.
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/nullHow 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.
<?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