KennyG
Aug 20, 2026
visibility 35
star star star star star
(0 votes)

How to Get a Bacpac Out of DXP (And Never Do It By Hand Again)

Every Optimizely DXP project eventually runs into the same task: getting production data onto a local machine so you can actually troubleshoot something. Real content. Real catalog data. Real edge cases that no seed script ever thinks to include.

At first, most teams handle it the same way. Someone logs into the portal, downloads a couple of multi-gigabyte files, runs SqlPackage from memory, and hopes nothing goes sideways.

After doing that enough times myself, I automated the process by teaching an AI coding agent the entire workflow and packaging it as a Claude Code skill. This post walks through where bacpacs actually come from in DXP, how we retrieve them, and what the skill does once it has them.

Why bother with real data locally at all?

The case for it

Seed data lies to you.

It's clean, it's small, and it never contains the malformed catalog entry or orphaned content reference that's sitting in production right now waiting to expose a problem you haven't seen yet. Restoring epicms and epicommerce from an actual DXP export gives you the real shape of the data: actual row counts, weird nulls, years-old content nobody has touched, and all the other surprises that come with a live system.

The only downside is that the process is tedious enough that most teams don't do it nearly as often as they should. That's the part worth automating.

This also turned out to be a great use case for an AI coding agent because the process is mostly orchestration. None of the individual steps are difficult, but there are enough of them that it's easy to miss one when you're doing it manually.

Where do bacpacs actually come from on DXP?

1. The portal, by hand

DXP's PaaS portal has a Troubleshoot page for each project where you can request and download a database export directly. This is where most teams start: log in, select an environment, wait for the export, and download it.

It works. It's also the slowest option because every step requires manual interaction.

2. The morning export, automated

On our project, a separate Azure DevOps release pipeline already generates fresh epicms and epicommerce bacpacs every morning and writes them into a DXP blob storage container.

That container has a container-scoped SAS token stored in the release definition variables with read and list permissions. As long as the token is refreshed periodically, grabbing the latest backups is as simple as:

./tools/get-dxp-backups.ps1
# -OutDir <dir> optional; defaults to Downloads

No Entra ID login. No portal navigation. No API credentials.

The script lists the container, finds the newest epicms* and epicommerce* blobs, and downloads both.

If your project already has a similar nightly export pipeline, it's worth looking through the release variables. You may already have everything you need.

3. A fresh export, on demand

Sometimes the overnight export isn't fresh enough. Maybe you're trying to verify whether today's deployment already ran, or you need data that changed recently.

In those cases, the EpiCloud PowerShell module can trigger a brand-new export directly from a DXP environment:

Import-Module EpiCloud

$ctx = @{
    ClientKey = $key
    ClientSecret = $secret
    ProjectId = $projectId
}

$export = Start-EpiDatabaseExport `
    @ctx `
    -Environment 'Production' `
    -DatabaseName 'epicms' `
    -RetentionHours 24

# Poll Get-EpiDatabaseExport until status is 'Succeeded',
# then download $status.downloadLink

This approach requires DXP API credentials created under Project → Settings → API in the DXP portal.

It also takes longer. A production-sized export can take 10 to 30 minutes before a file is even available to download.

I generally only use this option when the nightly export isn't fresh enough.

So what does the skill actually do?

Once it has a .bacpac for each database, the skill follows the same workflow every time:

  1. Download the newest pair of bacpacs and display their filenames, sizes, and timestamps.
  2. Display the plan before making any changes, including which databases will be affected, which mode will be used, and what will be dropped.
  3. Request confirmation when the planned action is destructive.
  4. Drop the target databases when running in replacement mode.
  5. Import both databases in parallel using SqlPackage /Action:Import.
  6. Verify the results using index health checks and sanity row counts against known tables, including tblContent for CMS and CatalogEntry for Commerce.
  7. Report freshness information using the source bacpac timestamps so you know exactly how current the restored data is.
  8. Clean up the multi-gigabyte bacpac files after the imports have been confirmed successful.

Running the imports in parallel usually means the total time is roughly equal to whichever database takes longer instead of the combined time of both imports.

What can I tell it to do differently?

The skill accepts plain-language instructions and maps them to four independent decisions.

Scope

Restore both databases by default, or restore only:

  • cms-only
  • commerce-only

Mode

Replace (default)

This drops the existing epicms and epicommerce databases and restores into those same database names. Nothing else in your local configuration has to change.

Additional, also called keep or side-by-side

This imports the bacpacs into new timestamped database names and leaves your existing databases untouched. It's useful when you're in the middle of something and don't want to disrupt whatever you're currently working on.

Source

blob (default)

This uses the automated morning export from blob storage.

fresh-export

This generates a new export directly from DXP, optionally from Production, Preproduction, Integration, or another environment.

Confirmation

Destructive operations always request confirmation unless you explicitly say something like:

  • just do it
  • force
  • don't wait for confirmation

So a request like:

Refresh my local databases.

runs the full default workflow.

While:

Restore commerce only from Preproduction and don't ask.

does exactly that.

Same skill. Same guardrails. Different scope.

How fast is this, really?

A production-sized import is still a heavy operation. Every index gets disabled before the bulk load and rebuilt afterward.

The biggest performance improvements come from a few places.

Run both restores in parallel

This is already the default behavior. The CMS and Commerce imports run at the same time, so the total duration is based mostly on whichever import takes longer.

Tune SQL Server once

A few local SQL Server configuration changes can help every subsequent restore:

  • Configure the model database with simple recovery and appropriately sized files so new databases don't immediately run into autogrow stalls.
  • Pre-size tempdb to avoid unnecessary growth during the index rebuild phase.
  • Increase max degree of parallelism so index rebuild operations can use the available CPU resources.

Monitor progress

Instead of staring at a blank terminal window, the bundled progress watcher follows the import logs and displays progress by phase:

  • Download
  • Data import
  • Index rebuild

That doesn't turn a multi-gigabyte restore into a five-minute operation.

It does mean the twenty-something minutes it takes no longer require constant supervision.

Worth stealing for your own project?

None of this is particularly Optimizely-specific.

Under the covers, it's a blob storage container, an Azure DevOps pipeline variable, and a PowerShell wrapper around SqlPackage.

What makes it worthwhile on a DXP project is that almost every team already has this process. The problem is that it's often tribal knowledge. One person knows where the exports live. Another remembers the right SqlPackage switches. Everybody else ends up asking the same questions every few months.

Turning How do I get fresh production data locally? into a reusable skill means that knowledge lives in the repository instead of in somebody's head. Anyone on the team can use it, and the process stays consistent every time.

If your project already has a nightly export pipeline, go check the release variables before building anything from scratch. There's a decent chance you've already got most of what you need.

Aug 20, 2026

Comments

error Please login to comment.
Latest blogs
Error FK_tblContentSoftlink_tblPropertyDefinition in CMS

Example Error :  Microsoft.Data.SqlClient.SqlException (0x80131904): The DELETE statement conflicted with the REFERENCE constraint "FK_tblContentSo...

Vinit Gavankar | Aug 20, 2026

Get count of pages, blocks , Assets from the CMS.

The following queries retrieve details from the CMS database. Make sure to try these queries locally first.    Step 1 — Discover your content type...

Vinit Gavankar | Aug 20, 2026

Optimizely CMP Login Failure After Enabling SSO – The Certificate Mismatch That Locked Everyone Out

Recently, our team faced a production issue where users suddenly lost access to Optimizely CMP, Opti ID Admin Center, and the Optimizely Support...

Madhu | Aug 19, 2026 |