How to Create a PrestaShop Staging Site
Complete guide to setting up a PrestaShop staging environment: Docker, shared hosting, and local dev options. Test updates safely before going live.
Why we run staging shops for every project we touch
At mypresta.rocks we run multiple staging environments side by side, including ps9-dev, ps8-dev, ps178-dev and ps176-dev, plus client-specific shops when a project needs its own clone. They're not a luxury. They're how we test every module update, every PrestaShop upgrade, and every theme change before any customer sees it. After a decade of shipping modules, we've never met a merchant who regretted setting one up, and plenty who regretted skipping it.
The pattern is always the same: a module update goes onto production untested, the homepage breaks at 9am, the support ticket lands at 9:05, and the next hour is spent debugging in front of real customers. A staging shop costs an hour to build and turns that scenario into a 20-minute fix on a URL nobody can see.
The cost of one hour of downtime during a sale beats the cost of maintaining a test environment by two orders of magnitude. If you sell on the internet for a living, you need a staging shop.
What a staging shop actually is
It's a full clone of your production shop (same files, same database, same module set) served from a URL only you and your team can reach. Same code paths your customers hit, same business logic, same edge cases. The only differences are the hostname, the credentials, and (if you've set it up properly) the fact that no real emails go out and no real cards get charged.
It's not a development environment for greenfield work, and it's not a backup. We've seen both of those misconceptions cost merchants real money: staging gets overwritten the minute you refresh it, so anything you built there and didn't push to production is gone.
What staging is
- A live copy of your production files and database
- On a separate domain or subdomain (we use
staging.yourshop.comordev.yourshop.com) - Locked behind IP whitelist or basic auth so only your team gets in
- Where module updates, theme changes, and PHP upgrades get rehearsed
What it isn't
- A development sandbox for building new code from scratch — that's a separate dev shop
- A backup. Backups are immutable. Staging gets clobbered every refresh.
- Set-and-forget. A staging shop that's six months out of sync with production catches nothing.
Option 1: Docker-based staging (what we use)
All our staging shops run on Docker on a single TrueNAS box. Each shop is one docker-compose stack on a shared Docker network — PrestaShop container, MySQL container, shared Redis, one bind mount for the html directory, one for MySQL data. We can spin up a new PS version in under five minutes, and tear it down to free up RAM when we're done with it. Once you've done this once, you'll never go back to "I'll just install it on a subfolder of cPanel."
Prerequisites
- A Linux host with at least 2GB RAM per staging shop (4GB if you want the admin to feel snappy)
- Docker and Docker Compose installed and working
- SSH into both production and staging
- You're comfortable copy-pasting shell commands
Step 1: the compose file
Create a directory and a docker-compose.yml:
mkdir ~/your-shop-staging && cd ~/your-shop-staging
cat > docker-compose.yml <<'EOF'
version: '3.8'
services:
prestashop:
image: prestashop/prestashop:8.2
container_name: <your-shop>
ports:
- "8080:80"
environment:
- DB_SERVER=db
- DB_NAME=prestashop
- DB_USER=root
- DB_PASSWD=your_secure_password
volumes:
- ./html:/var/www/html
depends_on:
- db
restart: unless-stopped
db:
image: mysql:5.7
container_name: <your-shop>-db
environment:
- MYSQL_ROOT_PASSWORD=your_secure_password
- MYSQL_DATABASE=prestashop
volumes:
- ./mysql:/var/lib/mysql
restart: unless-stopped
EOF
Match the PrestaShop image tag to your production version exactly. If prod is 1.7.8.11, use prestashop/prestashop:1.7.8.11, not 1.7. We've watched merchants chase phantom bugs for hours because staging was on 1.7.8.10 and prod on 1.7.8.11 — small enough to look identical, big enough to behave differently.
Step 2: dump production
SSH into prod and run mysqldump. Use --single-transaction on a live shop so you don't lock the orders table while customers are checking out:
# On your production server
mysqldump -u root -p prestashop > ~/prestashop_backup.sql
# Download to your local machine / staging server
scp user@production-server:~/prestashop_backup.sql ./
Step 3: import into staging
# Start the containers
docker compose up -d
# Wait ~30 seconds for MySQL to initialize, then import
docker exec -i <your-shop>-db mysql -u root -pyour_secure_password prestashop < prestashop_backup.sql
If MySQL complains about character set on import, set SET NAMES utf8mb4; at the top of the SQL file. We've spent more time than we'd like to admit debugging double-encoded UTF-8 on staging that didn't exist on production.
Step 4: copy production files
# Sync your production files to the staging html directory
rsync -avz --delete \\
user@production-server:/var/www/html/ \\
./html/ \\
--exclude='var/cache/*' \\
--exclude='var/logs/*' \\
--exclude='app/config/parameters.php'
Always exclude parameters.php from rsync. The DB credentials in there belong to production — if you let rsync copy them over, your staging shop tries to connect to your production database, which is the worst possible outcome of "setting up staging." We learned this the hard way on a client project in 2017 and have written the exclude flag automatically ever since.
Also exclude any runtime directories the shop writes to in production — sitemaps, generated images you don't need, backup folders, license keys. rsync --delete wipes the destination side, and overzealous rsyncs have erased generated sitemaps and broken Google Search Console more than once on our watch.
Step 5: rewrite URLs and clear cache
The single biggest "why doesn't my staging shop work" cause: ps_shop_url still points at production. Fix it:
docker exec -i <your-shop>-db mysql -u root -pyour_secure_password prestashop -e "
UPDATE ps_shop_url SET domain='staging.yourshop.com', domain_ssl='staging.yourshop.com' WHERE id_shop=1;
UPDATE ps_configuration SET value='staging.yourshop.com' WHERE name IN ('PS_SHOP_DOMAIN','PS_SHOP_DOMAIN_SSL');
"
Update html/app/config/parameters.php with the staging DB credentials from your compose file. Then nuke the cache:
docker exec <your-shop> rm -rf /var/www/html/var/cache/*
If you skip the cache clear, Smarty's compiled templates still embed the production URL and you'll get redirect loops back to the live shop. On any shop using OPcache (which is anything in production), restart PHP-FPM or hit the OPcache reset endpoint too.
Option 2: subdomain on shared hosting
Docker isn't available on cPanel, Plesk or DirectAdmin. You'll get the same result with a subdomain, a second database, and a file copy.
Create the subdomain
- In the hosting panel, go to Subdomains or Domains
- Add
staging.yourshop.com - Point it at a new directory like
/home/user/staging.yourshop.com
Create a separate database
- Open MySQL Databases
- Create something like
user_staging - Create or attach a user with full privileges on it
Copy files and import the dump
Over SSH:
cp -r /home/user/public_html/* /home/user/staging.yourshop.com/
# Export production
mysqldump -u user -p production_db > ~/staging_import.sql
# Import to staging
mysql -u user -p staging_db < ~/staging_import.sql
Edit app/config/parameters.php (or config/settings.inc.php on 1.6) to point at the new database, then run the same URL-rewrite SQL from Option 1, step 5. Don't forget to delete var/cache/ after.
Option 3: local development with XAMPP/MAMP
Fine for "let me check this module's admin screen for ten minutes" work, useless for anything else. The PHP version, MySQL version, file permission model, and OS-level extensions will all differ from your production server. We use local stacks ourselves, but never as the final test before deploy — anything that's about to touch production gets re-tested on a server-based staging that mirrors the prod environment.
Post-setup: the things that bite you if you forget
Kill outgoing email immediately
Your staging database has every real customer email address in it. Trigger a password reset, an order confirmation, or a stock notification cron, and those emails go to real customers. We've seen merchants apologise publicly because their staging cron sent "your order has shipped" to a thousand real customers whose orders had not, in fact, shipped.
Go to Advanced Parameters → Email and either:
- Set the method to "Never send emails" — the only fully safe option
- Route everything through Mailtrap so you can still inspect the rendered emails without delivering them
Disable payment modules
Stripe, PayPal, Klarna, Adyen — anything connected to a live merchant account. Either disable the modules entirely on staging, or switch each one to sandbox/test mode. The same applies to shipping APIs (UPS, DHL, GLS labels) and any module that calls an external service with billable side-effects.
Block search engines
A staging shop indexed by Google is a duplicate-content disaster. Three things to do, belt-and-braces:
- Disable the XML sitemap in Shop Parameters → Traffic & SEO
- Drop a
robots.txtwithUser-agent: *andDisallow: /at the root - Better still, block public access entirely (next section) so Google never reaches it in the first place
Lock it behind an IP whitelist
The most reliable approach is an Apache or Nginx-level IP restriction. Maintenance mode can be bypassed if the IP whitelist setting has a bug, and HTTP Basic Auth sometimes breaks AJAX-heavy modules (we've seen it kill our own admin panels when the browser couldn't carry the auth header through XHR). IP whitelist at the web server level catches everything before PrestaShop runs.
Keeping staging in sync with production
A staging shop that's three months stale is worse than no staging shop — it gives you false confidence. We refresh ours on a schedule and before any significant deploy.
When to refresh
- Before any meaningful change — module update, core upgrade, PHP version bump, theme rework
- Monthly minimum if you're actively developing
- After any big catalogue change on production — new category trees, structural product changes, feature/attribute reshuffles
Refresh script (Docker)
#!/bin/bash
# refresh-staging.sh — Pull latest production data into staging
# 1. Dump production DB
ssh production "mysqldump -u root -p'PASS' prestashop" > /tmp/staging_refresh.sql
# 2. Import to staging
docker exec -i <your-shop>-db mysql -u root -p'your_secure_password' prestashop < /tmp/staging_refresh.sql
# 3. Fix URLs
docker exec -i <your-shop>-db mysql -u root -p'your_secure_password' prestashop -e "
UPDATE ps_shop_url SET domain='staging.yourshop.com', domain_ssl='staging.yourshop.com' WHERE id_shop=1;
UPDATE ps_configuration SET value='staging.yourshop.com' WHERE name IN ('PS_SHOP_DOMAIN','PS_SHOP_DOMAIN_SSL');
"
# 4. Sync files
rsync -avz --delete production:/var/www/html/ ./html/ --exclude='var/cache/*' --exclude='app/config/parameters.php'
# 5. Clear cache
docker exec <your-shop> rm -rf /var/www/html/var/cache/*
echo "Staging refreshed."
The mistakes we still see merchants make
Production API keys left in place
The number of staging shops we've audited that were quietly sending real Stripe charges, hitting real shipping APIs for live tracking labels, or eating live API rate-limits because the keys came over with the database — too many to count. Rotate every external credential to a sandbox key on staging the moment the database lands, before you even open the back office.
Crons still firing
If you've set up cron jobs on production (abandoned cart reminders, stock sync, feed generation, license renewals) check that your hosting panel or systemd timers aren't running the same jobs on the staging hostname. We had a client whose staging shop was happily sending real Allegro stock updates for two weeks before anyone noticed.
Both back offices open in the same browser
PrestaShop's admin cookies are scoped to the domain, but session conflicts still happen when you've got both shops authenticated in the same browser. Use a separate browser, a separate profile, or an incognito window for staging. Every member of our team has a dedicated Firefox profile for client staging shops.
When to test on staging vs. when production is fine
| Always stage first | Safe directly on production |
|---|---|
| PrestaShop core upgrades (1.7 → 8, 8 → 9) | Content edits — CMS pages, product copy |
| Module installs or major version bumps | Price changes |
| Theme upgrades or child-theme work | Toggling already-tested modules on/off |
| PHP version bumps | Adding new products, categories, tags |
| Custom code, overrides, service decorators | Shipping or tax rule edits |
| Database migrations or structural SQL | Translation tweaks |
Worth the hour
The first staging setup takes about an hour. A refresh, once the script's in place, is five minutes. The first time staging catches a module update that would have broken your checkout, you've recouped the investment ten times over — and we've watched it happen on real shops we run. Docker is what we'd reach for if you have a VPS or dedicated box; subdomain cloning is the right call on shared hosting. Either way, the goal is the same: nothing untested touches your customers.
If you're setting up staging specifically to test our modules, our Try Before You Buy programme gives you a full 30-day demo of any module — install it on staging, kick the tyres, decide before you pay.
Related reading
- How to Test PrestaShop Modules Properly — what to actually do once staging is up
- PrestaShop Local Development: XAMPP, WAMP, Docker & Linux Setup — the local dev step before staging
- Docker for PrestaShop: Development Environment Setup — how we run our containers in detail
- PrestaShop Docker Setup: Development and Production Environments
- Delete Test Orders: Cleaning Up Your PrestaShop Before Going Live
Related modules
- Password Protection — lock the staging shop behind a password / IP whitelist
- MPR Cron Manager — control which crons fire on staging
- Performance Revolution — test performance changes before they hit production