Guides Guide

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.com or dev.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

  1. In the hosting panel, go to Subdomains or Domains
  2. Add staging.yourshop.com
  3. Point it at a new directory like /home/user/staging.yourshop.com

Create a separate database

  1. Open MySQL Databases
  2. Create something like user_staging
  3. 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.txt with User-agent: * and Disallow: / 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 firstSafe directly on production
PrestaShop core upgrades (1.7 → 8, 8 → 9)Content edits — CMS pages, product copy
Module installs or major version bumpsPrice changes
Theme upgrades or child-theme workToggling already-tested modules on/off
PHP version bumpsAdding new products, categories, tags
Custom code, overrides, service decoratorsShipping or tax rule edits
Database migrations or structural SQLTranslation 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

Related modules

Loading...
Back to top