Skip to content

Frameworks

Govard detects supported frameworks and applies runtime defaults plus version-aware overrides.


Support Matrix

FrameworkAuto-DetectionVersion-Aware ProfileDefault Web RootAudit Lint (govard)Deploy Recipe
Magento 2/pub
Mage-OSframework defaults/pub✅ (via Magento 2)✅ (via Magento 2)
Magento 1 / OpenMageframework defaultsproject root
Laravel/public
Next.jsframework defaultsproject root
Emdashframework defaultsproject root
Drupal/web
Symfony/public
Shopwareframework defaults/public
CakePHPframework defaults/webroot
PrestaShopframework defaultsproject root
WordPress/
Djangoframework defaultsproject root
Dagsterframework defaultsproject root
Custommanualmanualproject root

Linter column: Audit Lint (govard) shows govard audit run --checks lint support. ✅ = native govard provider (Magento2: Magento2 CS, Laravel: PSR12, Symfony: Symfony, WordPress: WordPress); every lint run also enforces the media guard (pub/media *.php/*.phtml/*.phtM2-LINT-MEDIA failed, container media-guard phase plus host ScanMediaGuard fallback) and hygiene .gitignore (pub/media/*.php etc. via internal/blueprints/files/.gitignore). See Audit — Lint & Profiler.

Deploy Recipe column: a ✅ framework registers its own recipe, so govard deploy fills the neutral pipeline with the commands that application actually needs — each framework section below lists them. A framework still deploys, but through the engine's default recipe: release creation, shared files, permissions, activation, verification, rollback — the steps that are framework-neutral. The application steps (dependencies, migrations, caches) are not filled in, because the engine will not guess a command an application did not declare; a project on one of those frameworks anchors deploy.hooks on the task ids instead. See Deployment for the pipeline and Deploy recipes for the four that ship one.


Runtime Defaults

FrameworkPHPNodePythonDBCacheSearchQueue
Magento 28.424mariadb 11.4valkey 8.0.0opensearch 2.19.0none
Mage-OS8.424mariadb 11.8redis 7.4opensearch 3.0none
Magento 1 / OpenMage8.1mariadb 10.11nonenonenone
Laravel8.4mariadb 11.4nonenonenone
Next.js24nonenonenonenone
Emdash22nonenonenonenone
Drupal8.4mariadb 11.4nonenonenone
Symfony8.4mariadb 11.4nonenonenone
Shopware8.4mariadb 11.4nonenonenone
CakePHP8.4mariadb 11.4nonenonenone
PrestaShop8.1mariadb 10.11nonenonenone
WordPress8.3mariadb 11.4nonenonenone
Django3.12postgres 16nonenonenone
Dagster3.12postgres 16nonenonenone
Custom8.4mariadb 11.4nonenonenone

means Govard does not force a default for that stack component.


Version-Aware Overrides

FrameworkVersionPHP OverrideOther
Laravel108.1
Laravel118.3
Laravel128.4
Symfony6.0-6.38.1
Symfony6.4+8.2
Symfony7.0-7.18.2
Symfony7.2+8.3
Drupal108.3
Drupal118.4
WordPress6.08.0MariaDB 10.6
WordPress6.38.1MariaDB 10.6
WordPress6.4-6.58.2MariaDB 10.11
WordPress6.6+8.3MariaDB 10.11
WordPress6 (bare)8.3MariaDB 10.11 — bare major resolves to latest minor (6.6+)
WordPress78.4MariaDB 11.4
Magento 22.4.9+8.4MariaDB 11.4, Redis 7.2, OpenSearch 3.0.0, RabbitMQ 4.1.0
Magento 22.4.88.4MariaDB 11.4, Redis 7.2, OpenSearch 2.19.0 or 3.0.0
Magento 22.4.78.3MariaDB 10.6 or 10.11, Redis 7.2, OpenSearch 2.12.0-2.19.0
Magento 22.4.68.2MariaDB 10.6 or 10.11, Redis 7.0-7.2, OpenSearch 2.5.0-2.19.0

Audit lint: govard audit run --checks lint (provider govard) is supported for Magento 2, Laravel, Symfony, and WordPress — 4 frameworks. audit lint supported for 4. Image govard-glint bundles native coding standards (WPCS 3.1 for WordPress, Symfony CS for Symfony, PSR12 for Laravel, Magento2 for Magento) + phpstan/phpstan-symfony + phpstan/phpstan-wordpress so WordPress/Symfony run natively without fallback to PSR12.

bash
# Inspect the resolved profile
govard config profile --json
govard config profile --framework laravel --framework-version 11 --json
# Matrix: ProjectPHPVersions [8.1-8.4], PHPStanLevel 5, Linters [phpcs,phpstan]

🧱 Magento 2

Magento 2 is the deepest supported workflow in Govard.

Key Features

  • govard config auto injects DB, cache, search, Varnish, and base URLs into app/etc/env.php
  • govard tool magento [command] runs Magento CLI (bin/magento) inside the PHP container
  • govard tool magerun [command] (Shortcut: mr) runs n98-magerun2 inside the PHP container
  • govard tool magento cron:install installs crontabs inside the container
  • Optional Selenium/MFTF support (mftf: true in features)
  • Optional built-in frontend synchronization (frontend_sync: true in features)
  • Dedicated php-debug routing when Xdebug is enabled

Typical Flow

bash
govard env up
govard config auto
govard tool magento cache:clean
govard test phpunit

🏎️ Frontend Synchronization

Magento 2 and Mage-OS projects can opt into Govard's built-in BrowserSync frontend synchronization:

yaml
stack:
  features:
    frontend_sync: true

frontend_sync defaults to false and is invalid for other frameworks. It enables explicit, project-owned frontend runtime discovery. govard env up never starts or allocates frontend development services and always routes the project domain to web or Varnish; the on-demand frontend lifecycle is managed separately.

Hyva projects require exactly one scripts.browser-sync owner below app/design/frontend/<Vendor>/<Theme>/web/tailwind; every discovered Tailwind package needs a committed package-lock.json. Luma projects require root Gruntfile.js, package.json, and package-lock.json (Magento ships these as .sample files — copy them and run npm install to generate the lockfile); Magento's default Gruntfile/LiveReload setup needs no further changes. A project may not satisfy both Hyva and Luma discovery at once; remove one setup before starting frontend sync. Govard does not create or edit BrowserSync, Magento, or theme files in your project.

Luma needs no project-specific BrowserSync config, but Hyva's browser-sync.config.js (owned by the theme, not Govard) must read the environment Govard injects and use these settings to work correctly as a reverse proxy in front of the real application:

js
module.exports = {
  proxy: {
    target: process.env.GOVARD_FRONTEND_SYNC_TARGET, // Govard sets this to the app container
    proxyOptions: { changeOrigin: false },            // keep the original Host header
    cookies: { stripDomain: false },                  // keep Magento's session cookie domain intact
  },
  port: Number.parseInt(process.env.GOVARD_FRONTEND_SYNC_PORT || '3000', 10),
  open: false,                                        // no display inside the container
  socket: {
    domain: "//'+location.host+'",                    // resolve at request time via Caddy
    path: '/browser-sync/socket.io',                   // must match the /browser-sync/* route
  },
};

changeOrigin: false and cookies.stripDomain: false are the two settings that matter most: with either wrong, BrowserSync sends Magento a Host header or cookie scope that does not match the browsing domain, which can trigger Magento's own base-URL redirect or break session cookies even though frontend start reports success. open: false avoids BrowserSync trying to launch a browser inside the headless Node container. socket.path must stay /browser-sync/socket.io since that is the only path Govard's Caddy route proxies to the BrowserSync container.

Switching the active Hyva theme

Because discovery requires exactly one scripts.browser-sync owner, a project with multiple Hyva themes can only drive frontend sync from one of them at a time. To switch: remove (or rename) the scripts.browser-sync entry in the currently active theme's web/tailwind/package.json, add the same script (plus its own browser-sync.config.js, package.json scripts, and package-lock.json) to the target theme, then run govard frontend start again — it re-discovers whichever theme is now the sole owner.

A theme that only inherits another Hyva theme via theme.xml's <parent> (and does not override web/tailwind itself) shares that parent's compiled CSS and does not need its own BrowserSync setup at all; frontend sync for the parent theme already covers it through Magento's own static-file fallback.

A non-Hyva theme (Luma-based, no Hyva_Theme dependency) cannot be driven through the Hyva path above — it needs the Luma prerequisites (root Gruntfile.js/package.json/package-lock.json) instead, and Hyva/Luma discovery remains mutually exclusive project-wide: only one of the two setups may be valid at a time.

After govard env up, use govard frontend start; use govard frontend logs [service] -f for BrowserSync, LiveReload, its HTML injector, or a discovered watcher (services are named sync, watch-<theme>, and inject — containers come out as <project>-frontend-sync-1, etc.), and govard frontend stop to remove only frontend services while retaining their dependency volumes. While active, Caddy routes Hyva /browser-sync/* requests to port 3000 and Luma /livereload/* requests to port 35729 for client assets and the socket; both modes also send all other application traffic through their own dedicated injector, which buffers only HTML responses to insert their client script (BrowserSync's or the standard LiveReload client) before </body> and passes non-HTML bodies through unchanged. Stopping restores the normal application route before the frontend containers are removed.

Native Upgrade Pipeline

bash
# Test upgrade in an isolated profile
cp .govard.yml .govard.upgrade-test.yml
GOVARD_ENV=upgrade-test govard upgrade --version 2.4.8-p4 --dry-run
GOVARD_ENV=upgrade-test govard upgrade --version 2.4.8-p4

What govard upgrade does for Magento 2:

  • Resolves correct PHP/MariaDB/Search versions for the target
  • Smart Composer merge (preserves your modules and custom repos)
  • Automatically relaxes version constraints for dev tools (phpunit, phpmd)
  • Handles composer update, setup:upgrade, and static content compilation

Multi-Website / Multi-Store Setup

yaml
framework: "magento2"
domain: "primary.test"
store_domains:
  store-a.test:
    code: base
    type: website
  store-b.test:
    code: store_b
    type: store
bash
govard domain add store-a.test
govard domain add store-b.test
govard config auto
govard tool magento cache:flush

What Govard handles automatically:

  • Routes all domains through the shared proxy with HTTPS
  • Sets global base URL from domain
  • Runs scoped bin/magento config:set for each store_domains entry
  • Emits MAGE_RUN_CODE / MAGE_RUN_TYPE host mappings (object form with explicit type)

What you still need to do:

  • Create websites, stores, and store views in Magento admin
  • Clear config/cache after changing store mappings

🌱 Mage-OS

Mage-OS is a community-maintained, drop-in fork of Magento 2 Open Source. Govard detects it via mage-os/product-community-edition or mage-os/project-community-edition in composer.json, and reuses Magento 2's Docker image, nginx template, and Varnish/compose stack — all Magento 2 tooling above (govard tool magento, govard tool magerun, govard config auto, multi-site routing) applies unchanged.

Default runtime: PHP 8.4.

Fresh Bootstrap & Native Upgrade Pipeline

bash
govard bootstrap --framework mageos --fresh
govard upgrade --version 1.3.1

govard bootstrap/govard upgrade use mage-os/project-community-edition and Mage-OS's public repository (https://repo.mage-os.org) instead of Magento's private repository.


🛒 Magento 1 / OpenMage

bash
govard tool magerun [command]

Default runtime: PHP 8.1 + MariaDB 10.11. No optional cache/search/queue services forced.

Native Upgrade Pipeline

bash
govard upgrade --version <version>

Handles: Composer sync, cache purge (var/cache, var/session, etc.), compiler maintenance, and DB migration via n98-magerun.

Multi-Store with Typed Routing

yaml
framework: "magento1"
domain: "primary.test"
store_domains:
  store-a.test:
    code: base
    type: website
  store-b.test:
    code: store_b
    type: store
  store-c.test: store_c   # scalar = legacy behavior (try both website + store code)

Object form with explicit type causes Govard to inject host-based MAGE_RUN_CODE / MAGE_RUN_TYPE into nginx/Apache automatically — no manual .htaccess SetEnvIf rules needed.


🎨 Laravel

bash
govard tool artisan [command]

Defaults: web root /public, MariaDB 11.4, version-aware PHP.

Native Upgrade Pipeline

bash
govard upgrade --version 12
  • Updates composer.json framework constraint
  • Runs full composer update
  • Runs php artisan migrate --force

Deployment

Laravel ships the deploy recipe laravel, so govard deploy runs Laravel's own commands rather than the engine's neutral defaults:

bash
govard deploy check production     # what the target implies, before anything runs
govard deploy production --yes
StepCommand on the target
build:vendorscomposer install --no-dev --optimize-autoloader --no-interaction --prefer-dist
build:frontendfrontend_command inside each frontend_dir; empty frontend_dir skips the step
app:configureartisan storage:link
db:migrateartisan migrate --force
maintenance:enable / disableartisan down / artisan up, run in the served release
app:workers:pausewith worker_control on: artisan queue:restart, plus horizon:terminate when Horizon is installed
app:cache:flushartisan optimize:clear then artisan optimize
deploy:verify (app)artisan db:show; migrate:status on Laravel 10 and older
db:backup— (no dump command: --db-backup fails and names the reason)

.env is a shared file and storage a shared directory, because the maintenance flag lives at storage/framework/down — a shared directory is what carries it across the release swap. sync_paths is vendor and public/build for an in-place docroot. The recipe declares four settings (frontend_dir, frontend_command, worker_control, runtime_reload_command) and asks the sandbox for default-mysql-client, the Laravel extension set (bcmath, curl, gd, intl, mbstring, mysql, sqlite3, xml, zip) and the mariadb + redis-server services.

The caches are built on the target, never on a build machine: artisan optimize writes bootstrap/cache/config.php, and once that file exists the process environment no longer overrides .env — a cache built elsewhere would carry another machine's configuration to production.

Full command list and rationale: Laravel, Symfony and WordPress. Worked configuration: Case 9 — Laravel, Vite frontend, symlinked webroot.


🌐 Drupal

bash
govard tool drush [command]

Defaults: web root /web, MariaDB 11.4, version-aware PHP.


⚡ Symfony

bash
govard tool symfony [command]

Defaults: web root /public, MariaDB 11.4, version-aware PHP.

Native Upgrade Pipeline

bash
govard upgrade --version 7
  • Updates symfony/framework-bundle constraints
  • Runs composer update
  • Runs doctrine:migrations:migrate
  • Runs cache:clear

Deployment

Symfony ships the deploy recipe symfony. It exists mainly to undo Composer's auto-scripts, which run cache:clear and assets:install in the wrong place:

bash
govard deploy check production
govard deploy production --yes
StepCommand on the target
build:vendorscomposer install --no-dev --optimize-autoloader --no-interaction --prefer-dist --no-scripts
build:assetsbin/console assets:install public --symlink --relative — marked needs the application, so the target runs it even in artifact mode
build:frontendfrontend_command inside each frontend_dir; empty frontend_dir skips the step
db:migratedoctrine:migrations:migrate --env=… --no-interaction --allow-no-migration
app:cache:flushcache:clear --env=… --no-warmup then cache:warmup --env=…
app:workers:pausewith worker_control on: messenger:stop-workers --env=…
maintenance:enable / disableempty — Symfony has no core mechanism, so both steps are reported as skipped
deploy:verify (app)dbal:run-sql "SELECT 1"; doctrine:query:sql on an older DoctrineBundle
db:backup— (no dump command: --db-backup fails and names the reason)

Every bin/console command above takes --env from the symfony_env setting:

bash
bin/console doctrine:migrations:migrate --env={{settings.symfony_env}} --no-interaction --allow-no-migration
bin/console cache:clear --env={{settings.symfony_env}} --no-warmup
bin/console messenger:stop-workers --env={{settings.symfony_env}}

.env.local is a shared file and var/log a shared directory; var/cache is deliberately not shared, because the compiled container belongs to one release and one environment. sync_paths is vendor and public/bundles for an in-place docroot. symfony_env (default prod) decides the environment every console command runs under and is not validated — a typo builds the wrong cache directory. The sandbox gets default-mysql-client, the extension set (intl, mysql, mbstring, xml, curl, zip) and the mariadb + redis-server services.

Because the maintenance tasks are empty, db:migrate runs against a live site; a project that needs a window adds it with two deploy.hooks on maintenance:enable / maintenance:disable.

Full command list and rationale: Laravel, Symfony and WordPress. Worked configuration: Case 10 — Symfony, Doctrine migrations, PostgreSQL target.


🛍️ Shopware

bash
govard tool shopware [command]

Defaults: web root /public, MariaDB 11.4, PHP 8.4.


🍰 CakePHP

bash
govard tool cake [command]

Defaults: web root /webroot, MariaDB 11.4.


🏪 PrestaShop

bash
govard tool prestashop [command]

Defaults: web root project root, MariaDB 10.11, PHP 8.1. Govard auto-detects PrestaShop projects and clones/configures existing installs; there is no fresh-install bootstrap or native upgrade pipeline for PrestaShop yet.


📰 WordPress

bash
govard tool wp [command]

Defaults: web root /, MariaDB 11.4, PHP 8.3.

Fresh Bootstrap

WordPress fresh bootstrap downloads core from wordpress.org and installs via PHP bootstrap scripts — wp-cli is not required for initial setup.

bash
govard bootstrap --framework wordpress --fresh

Native Upgrade Pipeline

bash
govard upgrade --version 6.7
  • wp core update --version=<version>
  • wp core update-db
  • wp cache flush

Deployment

WordPress ships the deploy recipe wordpress, for the classic layout only: core files and wp-content/ in the repository root, no composer.json. A Bedrock layout (core in vendor/, docroot web/) and a content-only checkout are not supported.

bash
govard deploy check production
govard deploy production --yes
StepCommand on the target
build:vendorscomposer install … only when composer.json exists (a guarded step, not || true)
build:frontendfrontend_command inside each frontend_dir; empty frontend_dir skips the step
db:migratewp core update-db, or wp_upgrade() through wp-load.php when the target has no wp-cli
app:cache:flushwp cache flush + wp rewrite flush --hard, or wp_cache_flush() + flush_rewrite_rules(true)
maintenance:enable / disablewrites/removes .maintenance and a marked wp-content/maintenance.php in the served path
db:backup / restorewp db export / wp db import — the only recipe besides Magento's with a dump command
deploy:verify (app)wp core is-installed, or is_blog_installed() without wp-cli

Three tasks are hybrids, each running wp when the target has wp-cli and a wp-load.php PHP bootstrap when it does not: wp-cli is what a real server has, but govard cannot install it on a target, and a deploy must not fail for a reason that has nothing to do with the release.

wp-config.php is a shared file and wp-content/uploads a shared directory; wp-content/cache, upgrade and languages are writable. sync_paths is vendor, which only matters for a project managing plugins or themes through Composer. The sandbox gets default-mysql-client, the extension set (mysqli, curl, gd, intl, mbstring, xml, zip), the mariadb + redis-server services and the wp-cli tool.

Two things to know before the first deploy: seed shared/wp-config.php, because deploy:shared links a shared entry only when it already exists — an unseeded shared/ leaves the first release with the repository's wp-config.php, the one naming the development database. And the maintenance flag is written with time() + 86400 rather than WordPress's own time(), because wp_is_maintenance_mode() expires a flag older than ten minutes: a longer deploy window would otherwise reopen the site mid-migration.

Full command list and rationale: Laravel, Symfony and WordPress. Worked configuration: Case 11 — WordPress, classic layout, wp-cli on the target.


⚡ Next.js

bash
govard shell   # opens web container at /app
govard tool npm [command]
govard tool npx [command]

Defaults: Node 24, no DB forced. Project-root web serving. govard tool npm and govard tool npx use the configured standalone Node image; govard shell continues to open the application web container.


🔵 Emdash

Node-first local runtime: Node 22, no managed PHP/DB/cache/search/queue.

bash
govard shell           # web container at /app
govard tool pnpm [command]
govard open admin      # opens /_emdash/admin

Fresh install:

bash
govard bootstrap --framework emdash --fresh
govard env up

Package manager auto-detection: Govard reads package.json (packageManager field), pnpm-workspace.yaml, and lockfiles.

Current scope is local Node + SQLite + local uploads. Govard does not yet automate Cloudflare D1/R2 flows.


🐍 Django

Python-first local runtime: Python 3.12 (configurable via stack.python_version), PostgreSQL 16, no managed PHP/cache/search/queue.

bash
govard shell           # web container at /app
govard tool manage [command]   # python manage.py [command]
govard db connect               # psql into the postgres db

Fresh install (scaffold a brand-new project from scratch):

bash
mkdir myproject && cd myproject
govard init --framework django
govard bootstrap --fresh --framework django --framework-version 5.1

Fresh install (clone an existing project, then bootstrap it):

bash
git clone <your-django-repo> myproject && cd myproject
govard init --framework django
govard env up
govard bootstrap --framework django

Detection: any project with a manage.py file at its root.

Current scope is requirements.txt + pip only (no Poetry/pyproject.toml), PostgreSQL only (no SQLite/MySQL option), and manage.py runserver for local dev (no Gunicorn). Both workflows run pip install + manage.py migrate automatically. --fresh scaffolds via django-admin startproject config . and wires settings.py to the Postgres container Govard already provisions, plus ALLOWED_HOSTS/CSRF_TRUSTED_ORIGINS for the project's configured domain.


⚙️ Dagster

Python-first local runtime: Python 3.12 (configurable via stack.python_version), PostgreSQL 16, no managed PHP/cache/search/queue.

bash
govard shell                    # web container at /app
govard tool dagster [command]   # dagster CLI commands
govard db connect               # psql into the postgres db

Fresh install (scaffold a brand-new project from scratch):

bash
mkdir myproject && cd myproject
govard init --framework dagster
govard bootstrap --fresh --framework dagster

Fresh install (clone an existing project, then bootstrap it):

bash
git clone <your-dagster-repo> myproject && cd myproject
govard init --framework dagster
govard env up
govard bootstrap --framework dagster

Detection: any project with a workspace.yaml or dagster.yaml file at its root.

Current scope is requirements.txt + pip only (no Poetry/pyproject.toml) and PostgreSQL only, via dagster-postgres. Both workflows run pip install automatically. --fresh scaffolds via dagster project scaffold and writes dagster.yaml (Postgres storage config wired to the container Govard already provisions) plus a workspace.yaml pointing at the scaffolded module. The compose blueprint also mounts and trusts Govard's local root CA inside the container, so outbound HTTPS calls to other Govard projects via linked_projects verify correctly.


🔧 Custom Stack

bash
govard init --framework custom

Interactive picker for:

  • Web server (nginx, apache, hybrid)
  • Database engine and version
  • Cache service
  • Search engine
  • Queue service
  • Optional Varnish

Contributing a New Framework

Want to add a framework not listed here? See Adding a New Framework for the internal registry structure and a file-by-file guide.


← Configuration | Remotes and Sync →

Released under the MIT License.