AEM Package Manager best practices for safe environment transfers
Moving code and content between AEM instances can feel like herding cats during a release window. A misnamed filter, an unchecked dependency, or a missed run-mode change has burnt many a delivery team at the eleventh hour, and the pain is rarely louder than when a package explodes halfway through a promotion to production. The Package Manager sits quietly in the corner of CRXDE and Felix, yet it carries the full weight of your delivery pipeline, so getting it right pays dividends long after the build server has cooled down.
Across Australian delivery teams, from Sydney banking squads to Brisbane government digital units, the difference between a calm Friday arvo and a chaotic weekend pager rotation often comes down to a handful of repeatable habits. This piece walks through the practical playbook that has held up in real AEM engagements, mixing the architectural thinking with the gritty details of filter roots, suppression rules, and the post-deployment smoke tests that catch trouble early.
Building clean package definitions
Every transfer starts with a package definition, and most production incidents start there too. The first habit worth forming is treating filter roots with discipline rather than dragging the entire /content tree into a single zip. Australian teams running multi-brand sites for the big four banks frequently segment by tenant folder, then by site, then by feature branch, so a hotfix can land without dragging forty unrelated runbooks along for the ride. Naming conventions matter as well: a consistent pattern like acme.web.brand-x.cq-author.v3.2.1 lets anyone grep the repo and know exactly what they are about to install, including which environment family it was built for.
The second habit is being honest about what should not move at all. Vault entries for /var/acs-commons, run mode configs locked to a specific tier, and dispatcher farm files all belong in different pipelines, not bundled into a single content package. When in doubt, split the package. The cost of two extra installs is trivial compared to the cost of a production roll-back triggered by an unintended config overwrite.
| Package type | Typical contents | Best suited for |
|---|---|---|
| Content | /content, /content/dam assets, design dialogs |
Promoting authored pages, asset bundles, and i18n dictionaries |
| Code | /apps, /libs overlays, OSGi configs in /config |
Deploying component logic, services, and run mode policies |
| Config-only | Repository-backed run mode nodes | Shifting environment-specific settings without touching code |
| Mixed | Combination of above, tightly scoped | Emergency hotfixes, used sparingly and heavily reviewed |
This separation mirrors how headless delivery through AEM Content Services expects content and presentation to travel along different rails, so designing your package taxonomy early pays off well past the first release.
Version control as the source of truth
A package that lives only on someone's laptop is a package waiting to cause grief. The strongest practice is to keep package definitions checked into the same Git repository as the code they reference, using the vault.config and META-INF/vault/filter.xml files committed alongside the Maven module. This gives you diffable history, peer review on every change, and the ability to rebuild a package byte-for-byte from a tagged commit, which is gold during a post-mortem when someone asks how a particular filter root ended up in a hotfix bundle.
Branch strategy matters too. Treating feature branches as the only place where new filter roots are introduced, and merging them through the same pull request as the code that needs them, prevents the classic problem of a developer adding a new component path on their local instance, packaging it, deploying, and then having the next person on the team unable to reproduce the build. Australian teams that pair this discipline with a trunk-based flow on shared feature branches tend to ship faster than those clinging to long-lived forks, especially when the team is split across Perth and eastern-state time zones and async reviews dominate.
Pre-flight validation before any transfer
Skipping validation is the single biggest cause of package manager regret. The minimum bar should include a dry run on a freshly minted local author instance, a checksum comparison against the build artifact, and a quick sanity check that the package does not include any paths flagged as sensitive, such as user profiles or replication agent credentials. A simple shell script that greps the unpacked filter.xml for forbidden roots will save your bacon on the day a junior dev accidentally drags /home into a content bundle.
Throttling and timeouts deserve a mention as well. Large /content/dam packages that take thirty minutes to install can collide with replication queues and leave you with a half-installed author. Splitting big asset bundles into thematic sub-packages, and scheduling them into a quiet maintenance window after standard AEST business hours, keeps the system responsive while the install churns through. Teams running overnight deploys from Sydney to offshore build farms will recognise the value of building in a buffer for the slow link before anyone in Melbourne logs in the next morning.
Things worth checking before you press install:
- Filter roots match the package name and intended scope
- No run mode nodes are sneaking past the
/config/appsboundary - Thumbnail and rendition binaries are excluded if they are regenerated downstream
- Package size stays under whatever your instance can chew through in one window
Designing staging tiers that catch what dev cannot
A staging environment that mirrors production closely is your last line of defence, and in Australia that often means at least two tiers: a pre-prod that matches prod hardware and config, and a UAT tier that matches the data shape and traffic profile. Promoting through these tiers in order is non-negotiable, and the package metadata should record which tier it last cleared so anyone auditing later can trace the path.
The other thing staging should do is break things on purpose. Synthetic load that simulates a Melbourne trading desk firing off content updates during peak, or a flaky mobile network from a Brisbane field worker, will surface replication and asset processing bugs that a quiet staging server never will. Once a package has cleared that gauntlet, it earns the right to be promoted, and the build pipeline should be able to point at the exact artifact rather than rebuilding from source.
Rollback strategy when things still go sideways
Even the best process will eventually ship a bad package, so a tested rollback plan is part of the best practice, not an afterthought. Snapshots taken immediately before each install, paired with a runbook that names the exact reversion package, give you a path back to a known-good state in minutes rather than hours. Where the underlying issue is data corruption rather than code, the rollback often needs to combine a code reversion with a content reversion, which is another argument for keeping those two package streams separate in the first place.
Recording rollback drills in the same place you record outages turns them from theoretical exercises into muscle memory. Teams that rehearse a rollback every quarter, even on a quiet Tuesday arvo, find that the real incident two months later feels far less like a fire and far more like a procedure. The discipline also surfaces gaps, such as a missing reverse filter root or a snapshot policy that quietly expired, before they bite you under pressure.
Signals worth watching during and after a transfer:
- Replication queue depth and age on both author and publish
- Bundle status across the OSGi console, especially anything marked "Installed" rather than "Active"
- Error log patterns, scoped by correlation id, that match the install window
- application performance trends pulled from New Relic or your APM of choice
Monitoring and observability after the install
The job is not done when the install reports success. A package can install cleanly and still leave the system in a degraded state, so the post-install window deserves the same rigour as the build pipeline. Watching bundle activation, replication queue depth, and request latency for at least one full business cycle gives you confidence that the transfer was genuinely clean. Teams that hook these signals into the same dashboards they use for general AEM health get earlier warnings and shorter mean time to detect.
For distributed teams, that observability loop has to cross time zones without losing context. A Sydney engineer finishing at six can leave a clearly labelled dashboard for a Brisbane colleague to check at eight, and an automated alert that fires into a shared channel keeps everyone honest regardless of who is at the desk. The cultural habit of writing notes directly into the deployment record, rather than in a private chat, means the next person on rotation has the same picture you had.
Post-deployment hygiene and the next package
Once the dust has settled, lock the lessons back into the pipeline. A short retro entry that captures what worked, what surprised you, and what the next package should do differently keeps the practice alive and stops the same mistake from recurring six months down the track. The teams that ship AEM reliably are rarely the ones with the cleverest setups; they are the ones whose feedback loops are short and honest.
If you are putting together your next AEM environment transfer and want to compare notes with practitioners who have shipped through similar pipelines, swing past the CIRCUIT event hub to see what the community has been recording and presenting. The session archive is a fair dinkum shortcut to lessons other teams paid to learn the hard way, and a good place to start refining your own runbook before the next release window opens.