Skip to content

Migrating from Adobe Commerce Cloud or Platform.sh

If you already ship Magento on Adobe Commerce Cloud (ACC) or Platform.sh / Upsun, MageLift should feel familiar at the edges and different in the middle. You still declare environments in YAML, promote an immutable build, and use CLI verbs for logs and shells. The cloud account, network, and managed services are yours. MageLift does not rent a PaaS for you.

Certified paths today: AWS ECS Fargate and the evidenced GCP GKE Autopilot cells. AWS EKS, GCP GKE Standard, OVH MKS, and Scaleway Kapsule stay experimental. Import maps allowlisted Magento configuration into application.magento, including an admin front name, cookie domain, CORS origins, and consumer mode or names when the source provides them. It does not invent websites, stores, or store views.

Mental model

ACC / Platform.sh idea MageLift equivalent
.magento.app.yaml / .platform.app.yaml magelift.yaml (portable Magento inputs)
Environment branches / multi-env environments.* overlays with inheritance
Variables / secrets Secret references (aws-secrets-manager://…), never plaintext
Build + deploy hooks Build pipeline + post-deploy Magento phases
magento-cloud / platform SSH magelift exec / magelift ssh (ECS Exec into web or cron)
Environment logs magelift logs --service web\|deploy\|cron
Cron workers ECS cron service + magelift cron-run
Local Lando / Docker magelift local Compose loop (account-free)

What stays the same

  • One config file in the Magento repo drives environments.
  • Builds produce a digest you promote; you do not rebuild for production.
  • Day-2 ops are CLI-first: health, logs, exec, cache flush, reindex.
  • Preview / staging / production are first-class environment classes.

What changes on purpose

  • You own the AWS account. Billing, IAM, DNS, and KMS keys are yours. Bootstrap creates the shared control-plane pieces; stacks stay in that account.
  • Topology is not YAML soup. Network CIDRs, RDS vs experimental Aurora, OpenSearch mode, and NAT mode are explicit escape hatches on target.aws, not hidden SKU swaps. Standard/HA YAML that omits queueMode defaults to ecs-rabbitmq; explicit amazon-mq remains experimental-warn. Certified queue cells are db and ecs-rabbitmq.
  • No inbound SSH. magelift ssh is ECS Exec. There is no bastion and no magelift tunnel on the Fargate path.
  • Deploy is not a shell service. Migrations run as a one-off candidate ECS task. Use magelift logs --service deploy for migrate output. exec only attaches to long-lived web or cron tasks.
  • Build cannot differ per environment. Artifacts are immutable; environment overlays change runtime config and capacity, not the image.

Side-by-side: ACC app YAML → MageLift

ACC / Upsun style (illustrative):

# .magento.app.yaml (shape only; do not paste into MageLift)
name: app
type: php:8.3
hooks:
  build: |
    set -e
    composer install
  deploy: |
    php bin/magento setup:upgrade
relationships:
  database: "mysql:mysql"
  redis: "redis:redis"
  rabbitmq: "rabbitmq:rabbitmq"

MageLift equivalent (portable inputs + AWS catalog escape hatches):

schemaVersion: 1
project:
  name: app
application:
  edition: open-source
  version: 2.4.9
  mode: integrated
build:
  php: "8.5"
  extensions: [apcu, bcmath, ftp, gd, intl, mbstring, opcache, pdo_mysql, redis, soap, sockets, sodium, xsl, zip]
  composer:
    version: "2.10"
    credentials: aws-secrets-manager://magelift/composer
target:
  provider: aws
  runtime: ecs-fargate
  aws:
    catalog:
      queueMode: ecs-rabbitmq   # or amazon-mq | db
defaults:
  region: eu-west-3
  preset: preview
environments:
  staging:
    account: "123456789012"
    branches: [main]
    domain: staging.example.com

Hooks stay typed Magento/build phases, not free-form shell strings in portable YAML. Variables become secret references. Topology (Aurora vs RDS, queue mode, NAT) lives under target.aws, not as PaaS relationship aliases.

Generate YAML from ACC / Upsun (shipped)

Offline importers generate reviewable MageLift YAML. They never accept foreign PaaS schemas as deploy input: magelift --config .magento.app.yaml … fails because config.Load requires schemaVersion: 1.

From the Magento project root (where .magento.app.yaml or .platform.app.yaml lives):

# Adobe Commerce Cloud → magelift.yaml (refuse if the path already exists)
magelift init --from-acc

# Upsun / Platform.sh
magelift init --from-upsun

# Overwrite an existing file only with explicit confirmation
magelift init --from-acc --yes

# Write a review copy without touching the default --config path
magelift init --from-acc --config-out review.magelift.yaml

--from-acc and --from-upsun are mutually exclusive. The mapper covers structural app/services/routes/cron plus the frozen D-07 env allowlist (crypt → encryption secret ref placeholder; UPDATE_URLS/routes → domain; DB/Redis/OpenSearch/AMQP relationships → capability / catalog intent; SCD_* → build.staticContent; and allowlisted CONFIG__DEFAULT__* values → Magento admin, cookie, CORS, and consumer overlays). See ece-tools parity.

The importer also carries ACC and Upsun PHP runtime.extensions into build.extensions, and the composer/composer PHP dependency into build.composer.version. The isolated builder checks both values before it runs Magento lifecycle steps. Unsupported PaaS settings remain in the unmapped sidecar.

When any key is outside that allowlist, MageLift still writes the YAML, writes a sidecar next to the output path (magelift.unmapped.md, or <stem>.unmapped.md when using --config-out), and exits non-zero. Fix or accept the residuals by hand before deploy.

Fastly migration intent

If the source service file declares a Fastly service, the importer writes an edge.externalProvider: fastly intent and keeps the route hostname as an edge domain. It never copies a Fastly token, private key, or VCL body. The unmapped sidecar names the missing serviceId and tokenSecret fields so an operator can add a secret reference deliberately:

edge:
  externalProvider: fastly
  serviceId: "123456"
  tokenSecret: aws-secrets-manager://magelift/fastly-api-token
  domains: [store.example.com]
  tls: true
  purgeOnDeploy: true
  # Optional reviewed VCL or policy artifact reference.
  vclRef: fastly/vcl/store.vcl

Fastly is an experimental migration target in RC1. Use an authenticated Fastly CLI profile and run magelift edge plan, magelift edge apply, and magelift edge destroy --yes to manage the declared domains. A normal origin deployment applies Fastly after its candidate and health gates pass. The first-party AWS, GCP, OVHcloud, and Scaleway stacks keep origin and edge as separate capabilities, so a Fastly request cannot silently become CloudFront.

The first Fastly account smoke is recorded in fastly-adapter-live-2026-08-13.md. It found that the authenticated account rejects the CLI's classic service-domain endpoint in favor of Domain Management. A disposable routed-domain test then proved TLS, active-version attachment, origin routing, purge, and teardown. The adapter uses the current fastly domain create, domain list, and domain delete --domain-id commands, reuses domains whose description matches the exact MageLift marker, and stores only ownership IDs in mode-0600 local state. Existing Fastly services are preserved unless the adapter created them. The live adapter lifecycle test proves the create, list, purge, and cleanup path, while full production edge certification remains open.

Phase 4 ships the config onramp above. Database dump import, media sync, and the cutover runbook below ship in Phase 5. Brownfield attach of an existing AWS VPC / RDS MySQL is Phase 8; see brownfield-attach.md (limits, egress ownership, detach). Multi-cloud / Cloud SQL attach remains later.

  1. Run magelift init --from-acc or --from-upsun; review YAML + any unmapped sidecar.
  2. Replace encryption / Composer secret placeholders with real Secrets Manager (or SSM) refs.
  3. Add preview, staging, and production environments as needed.
  4. Run magelift doctor and magelift bootstrap in a non-production AWS account.
  5. Build and promote one digest; deploy preview with destroy-on-exit acceptance (docs/aws-acceptance.md) until destroy leaves zero tagged leftovers.
  6. Follow the cutover runbook below before pointing production DNS at MageLift.

Cutover runbook (dump, media, maintenance, reindex, verification, rollback)

This runbook moves a store onto a MageLift-managed environment. Phase 5 proves the executable local half (dump import, media sync, maintenance/reindex/verify/rollback command shapes) offline. Live DNS cutover, a full non-prod rehearsal against a managed cloud instance, and the managed-instance dump-import acceptance cell ride paid follow-up gate; no fourth paid AWS pass in Phase 5.

Prerequisites

  • Valid magelift.yaml with a non-production environment (preview/staging) and seedDump set via magelift env create … --dump <path> (or an existing overlay).
  • Sanitized dump outside git; media tree available as a local directory.
  • Deployed environment with stack outputs including mediaBucket (AWS) and a reachable DB for import.
  • Day-2 path working: magelift exec, magelift reindex, magelift logs (operations).

1. Import the database dump

First successful deploy with seedDump status recorded auto-imports once (hybrid D-01). For explicit retries or dry runs of the same path:

# Journal: recorded|failed → importing → imported|failed
magelift env import-dump <environment>

# Retry into a non-empty database (MIGRATE-05 / D-04)
magelift --yes env import-dump <environment>

# Confirm journal
magelift env status <environment> -o json

Do not use magelift local seed for cloud environments; that verb is local Compose only.

2. Sync media into object storage

# Merge upload: PutObject only; remote extras kept; fails if source keys missing after list
magelift env media-sync <environment> --source /path/to/media-or-pub

Key mapping: if --source contains pub/media/, keys are relative to that prefix; otherwise --source is the media root. Offline evidence tier is unit + Floci listing-diff (see capability matrix); live bucket sync on a paid account is not claimed by Phase 5.

3. Magento maintenance mode

Put Magento into maintenance before DNS or final traffic shift so shoppers do not hit a half-cut store:

magelift exec --env <environment> --service web --container web -- \
  bin/magento maintenance:enable

# After verification (or on rollback):
magelift exec --env <environment> --service web --container web -- \
  bin/magento maintenance:disable

4. Reindex

magelift reindex --env <environment>
# equivalent fixed Magento path via ECS Exec:
# magelift exec --env <environment> --service web --container web -- bin/magento indexer:reindex

Optionally flush caches after reindex: magelift cache-flush --env <environment>.

5. Verification checks

Before DNS:

  1. magelift env status <environment> -o json: seedDumpStatus is imported (or intentionally absent if no dump).
  2. Storefront / admin health through the ALB or preview URL (magelift runtime health / browser smoke), not only Pulumi success.
  3. Sample SKUs, CMS pages, and a media URL from the synced bucket path.
  4. magelift logs --env <environment> --service web --since 30m for fatal Magento errors; magelift logs --service deploy if a migrate candidate ran.

6. DNS cutover

Point the customer domain (and ACM certificate validation, if needed) at the MageLift edge for that environment. DNS TTL, Route53/ACM wiring, and live non-prod rehearsal evidence are paid follow-up gate. Document the planned records here operationally, but do not treat Phase 5 docs or local scratch as SC5 live proof.

Cloudflare preview rehearsal (MIGRATE-04 / Phase 7)

Preferred non-prod host: set MAGELIFT_CUTOVER_HOST to an operator-owned FQDN you control (docs/examples use magelift-preview.example.com). Use an API token with Zone.DNS Edit (and Zone.Zone Read) via CLOUDFLARE_API_TOKEN or CF_API_TOKEN. Do not use Wrangler OAuth for DNS writes. Wrangler OAuth is zone:read only and cannot create/update records.

export CLOUDFLARE_API_TOKEN=...   # Zone.DNS Edit; not Wrangler OAuth
export MAGELIFT_CUTOVER_HOST=magelift-preview.example.com  # your zone
# Optional: MAGELIFT_CUTOVER_ZONE=example.com when auto-derive is wrong
# Point at stack applicationURL / LB hostname or IPv4 from the paid pass:
TARGET=<ingress-or-lb> ./scripts/cutover-dns-cloudflare.sh
# Rehearse without mutating:
TARGET=example.invalid ./scripts/cutover-dns-cloudflare.sh --dry-run
# Harness EXIT / teardown:
./scripts/cutover-dns-cloudflare.sh --cleanup

Auth and zone notes: use a Cloudflare API token with Zone.DNS Edit on the preview host zone; see evidence for a rehearsal that used cf CLI dns_records:edit and --cleanup.

7. Rollback

If verification fails after traffic shift:

  1. Revert DNS to the previous origin (lowest blast radius).
  2. Disable Magento maintenance on the previous origin if you enabled it there; keep MageLift in maintenance until you decide to retry.
  3. Infrastructure rollback for a disposable preview: magelift env destroy <name> --yes (protection must be off) or restore from magelift state backup/restore day-2 verbs when the env is long-lived; see operations.
  4. Re-import only with --yes when the target DB is non-empty; prefer a fresh preview env over overwriting production.

Evidence (MIGRATE-04 / SC5)

Slice Status
Runbook + local scratch (dump, media unit/Floci, maintenance/reindex/verify/rollback command shapes) Phase 5
Live DNS + full non-prod cutover rehearsal + managed-instance dump cell paid follow-up gate (no Phase 5 paid AWS pass)