I'm coming from a WordPress and PHP background, where I'm used to keeping the whole application in one Docker Compose project, developing there, and deploying from Git. I'm now learning a stack built around Next.js, React, Strapi, PostgreSQL, Puck, Docker Compose, and a Git-based deployment platform on my homelab.
My current project has three services: a frontend, a Strapi backend, and PostgreSQL. It works, but I'm unsure what a sensible development and deployment workflow looks like for a reusable template that could later be adapted for different clients.
At the moment, Next.js and Strapi run in development mode inside containers, with the source directories bind-mounted so I can edit over SSH. The same repository is also deployed remotely, which has made me question whether development and production should use the same Compose configuration.
I'd like to understand how people usually approach questions such as whether to run Node directly on the development machine or put the entire toolchain in Docker, how to separate development Compose files from production files, whether Next.js and Strapi belong in one monorepo, and whether production should use built images with next build and strapi start rather than development commands.
I'm also interested in database practices, such as using a local PostgreSQL container for development and a separate persistent database for staging and production, plus whether Dev Containers are preferable to manually entering containers. Practical examples and lessons learned would be especially useful.
4 Answers
The biggest Docker-specific trap is usually node_modules. If the host source directory is mounted over the application directory, it can hide the dependencies installed in the image. That often leads to native modules being built for the wrong operating system or architecture.
Mount the source for hot reloading, but use a separate named or anonymous volume for each container’s node_modules. For Next.js, giving .next its own volume can also prevent generated files from causing confusing permission and performance issues. The same pattern applies to Strapi’s generated files.
For production, remove all source mounts and use a multi-stage image that installs dependencies, builds the app, and contains only what is needed to run it.
For a reusable client template, make Strapi bootstrapping part of the project from the beginning. Content types and configuration may be represented in code, but roles, permissions, admin setup, and default records can still depend on the database. A fresh deployment can otherwise start with an empty or inaccessible API.
Create a seed or bootstrap routine that establishes the expected permissions and initial data, while keeping secrets and client-specific values in environment variables. Treat the database schema, seed process, migrations, media storage, and backup procedure as part of the template rather than manual setup steps.
Keep the three-service Compose project; that level of separation is perfectly reasonable. The important distinction is between development and production configuration. Development can use bind mounts, file watchers, and next dev or strapi develop. Production should build immutable images from Git and run the compiled application with no source mounts.
A common setup is a base Compose file plus a development override, or separate development and production files. Give the frontend and backend their own Dockerfiles, environment templates, health checks, and deployment steps even if they live in the same repository. A monorepo is convenient, but it isn’t mandatory.
Use a named volume for PostgreSQL data rather than a source-directory bind mount, and add a database health check so Strapi does not start before PostgreSQL is ready. Keep uploads on persistent storage or object storage instead of inside the application image, and handle backups and migrations explicitly.
Running everything in Docker is valid, especially when the development environment must be reproducible or you work across several machines. For a single developer, running Next.js and Strapi directly on the host while keeping PostgreSQL and infrastructure services in Docker can be faster, particularly because Strapi’s admin rebuild and filesystem watching may be slower through mounted volumes.
Dev Containers are a good middle ground if you want host-independent Node versions and editor integration. Whichever approach you choose, keep the commands and environment variables documented so switching between host-based development and container-based development does not become a project-specific mystery.

That makes sense. I was mainly worried that using a host-based Node setup would make the project less portable, but separating the application containers from the database and documenting the required versions may be simpler than forcing every watcher into Docker.