The Rebuild Problem Nobody Warns You About
Most PrestaShop Docker guides get you through installation and stop there. You spin up the container, run through the setup wizard, commit your first module, and everything works. Then you rebuild the image after a Dockerfile change or a docker compose down -v, and your override files are gone. Your custom module folder is empty. Git shows nothing to commit because git was never tracking the right paths in the first place.
This isn't a PrestaShop bug. It's a volume configuration problem, and it's almost always self-inflicted. If you're building custom modules or themes against a PrestaShop Docker environment, the way you mount volumes and the way you write your .gitignore file matter more than the install itself.
Why Containers Eat Your Code
Docker containers are ephemeral by design. Anything written inside the container's writable layer disappears the moment the container is removed, unless it lives on a volume. PrestaShop's official Docker image (the one documented under Docker Flashlight in the developer docs) installs the full application into the container filesystem during build or entrypoint execution. That includes modules/, themes/, override/, var/, and the config/ directory.
When you follow a basic install-only tutorial, you typically get one of two setups: either no volumes at all (everything lives inside the container and vanishes on removal), or a single bind mount of the entire PrestaShop root onto your host. Both are wrong for active development, just in different ways.
No volumes means you lose everything, obviously. But mounting the entire root is almost as bad. PrestaShop writes generated cache, compiled Smarty templates, Symfony cache, and vendor autoload files into that same tree during runtime. If your host bind mount overlays the whole install, you end up fighting file permission mismatches between your host user and the container's www-data user, and your .git repository ends up tracking thousands of files you never wrote, including vendor code and compiled cache artifacts.
The Fix: Mount Only What You Own
The correct approach is selective bind mounting. You mount your own working directories from the host into the container, and you let the container manage everything else with named volumes or the image's own filesystem.
Here's the directory split that actually matters for module and theme development:
modules/your-module-name— bind mount from your host repothemes/your-theme-name— bind mount from your host repooverride/— bind mount, since override classes are core to custom behavior and easy to losevar/cache,var/logs— leave inside the container or on an anonymous/named volume, never bind mountvendor/, coreadminfolder,config/settings.inc.php— leave inside the container, do not track in git
A docker-compose fragment for this looks roughly like:
services:
prestashop:
image: prestashop/prestashop:latest
volumes:
- ./modules/mycustommodule:/var/www/html/modules/mycustommodule
- ./themes/mytheme:/var/www/html/themes/mytheme
- ./override:/var/www/html/override
- prestashop_data:/var/www/html
volumes:
prestashop_data:
Notice the named volume prestashop_data sits underneath the whole install, giving you persistence for the core files you don't want to manage manually, while the bind mounts sit on top for the specific paths you're actively editing. This layering matters: Docker resolves bind mounts on top of named volumes at the mount point level, so your module folder wins even though the named volume also covers that path.
Handling the override Directory Correctly
Override files are one of the most commonly lost pieces of custom PrestaShop work, because developers forget that override/ isn't a module. It's a flat directory mapped directly into core class resolution. If you don't bind mount it explicitly and you only think to protect modules/, a container rebuild will silently wipe every override you wrote for Product, Cart, or any other core class. Always mount override/ as its own explicit bind mount, never assume it's covered by mounting modules/.
Themes Need the Same Treatment as Modules
Theme development has an extra wrinkle: compiled assets. If you're using a build step (Sass compilation, webpack, whatever your theme uses), your compiled CSS and JS often land in a subfolder inside the theme directory. Bind mount the whole theme folder, but be deliberate about what git tracks inside it — source files yes, compiled output usually no, unless your deployment pipeline expects pre-built assets in the repo.
Writing a .gitignore That Actually Protects You
Here's where most setups quietly fail. Developers either git-init at the PrestaShop root and end up tracking vendor code and cache files, or they git-init inside their module folder and lose the context of override files and theme changes living in sibling directories.
The cleanest pattern is to keep your git repository at the project root — the folder containing your docker-compose.yml — not inside the PrestaShop install itself. Your repo tracks your own modules/, themes/, override/, and docker-compose.yml, nothing else. The PrestaShop core lives in the named volume and is never part of your git history.
A practical .gitignore for this structure:
# Never track compiled or generated PrestaShop artifacts
var/cache/
var/logs/
vendor/
admin*/
config/settings.inc.php
# Theme build output, adjust to your build tool
themes/*/assets/compiled/
themes/*/node_modules/
# OS and editor noise
.DS_Store
.idea/
.vscode/
If you're bind mounting only modules/your-module, themes/your-theme, and override/, most of that generic ignore list becomes irrelevant anyway, because those paths never physically exist on your host. That's actually the safest state to be in — you can't accidentally commit vendor code if vendor code never touches your filesystem in the first place.
Common Mistakes That Cause Silent Data Loss
The first mistake is running docker compose down -v out of habit. The -v flag removes named volumes, including your prestashop_data volume holding the core install and database data if you haven't separated that out. Get in the habit of using docker compose down without -v for routine restarts, and reserve -v for genuine full resets.
The second mistake is editing files through a shell inside the container instead of on the host. If you docker exec into the container and edit a module file directly with vim, and that path isn't bind mounted, your edit disappears on the next container recreation even without a volume removal. Always edit on the host, through your bind-mounted path, so your IDE and git see the same files the container is running.
The third mistake is forgetting composer autoloading after adding new classes to a module. PrestaShop's vendor/autoload.php is generated inside the container. If you add a new PHP class to your module and it isn't being picked up, it's rarely a volume problem — it's usually that the container's Composer cache doesn't know about your new file path yet. Running the container's composer dump-autoload, or restarting the container so PrestaShop's own module registration re-scans, fixes this without touching your volume setup at all.
When This Setup Isn't Worth the Complexity
If you're only doing a quick proof-of-concept, or testing a third-party module you didn't write, the generic single-volume install approach is fine. Selective bind mounting adds real setup overhead, and it's not worth it for throwaway environments you'll delete in a day. It earns its complexity when you're maintaining a module or theme in its own git repository over weeks or months, across multiple rebuilds, possibly across multiple PrestaShop versions during testing.
Once you're at that point, the volume and gitignore structure described here stops being an optimization and becomes the thing that keeps your actual work from disappearing every time you touch the Dockerfile.