You read a tutorial: Node app, Redis, Postgres, Nginx, worker — five docker-compose containers copy-pasted. On an 8 GB VPS, the stack starts… and falls under real load because nobody sized resources or configured healthchecks. Docker was not the problem. Premature stacking was.
Docker solves one pain: "it worked on my machine." It freezes dependencies and the start command. To host a web app, start with one well-built application container — not a Rube Goldberg factory.
Step 1: a readable Dockerfile
Start from an official pinned base (node:20-alpine, php:8.3-fpm). Use a multi-stage build: one stage to compile assets, one minimal runtime stage. Run the final process as a non-root user, add a simple HTTP HEALTHCHECK, and keep an aggressive .dockerignore — no local node_modules or embedded .git.
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
USER node
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
A Dockerfile is a runtime contract. If it exceeds one page, simplify the app or split services later.
Step 2: data outside the container
Uploads must not live in the container ephemeral layer: use a volume or object storage. SQL must not run without a named volume unless you prefer managed database at the host. Logs can go to stdout, but plan external aggregation. Secrets must never be baked into the image: inject them at runtime, as the guide Deployment secrets reminds.
On a small project, app in Docker, managed Postgres at the host avoids operating a database on the same VPS.
Step 3: docker-compose only when needed
Compose makes sense for local development or a small single-tenant VPS with two or three coupled services. In production, keep it if you self-host Redis and the app on one machine, or if you need an internal app ↔ redis network without exposing Redis publicly. Otherwise one app service behind a reverse proxy (Caddy or Nginx on the host, or a single reverse-proxy container) is enough.
Deploy on VPS: simple pattern
Build the image in CI, push to a registry (GHCR, Scaleway Registry…), then on the VPS run docker pull and docker run with volumes and --restart unless-stopped. Terminate TLS on a host reverse proxy pointing to the container port. Centralize logs via docker logs or an agent, and update by redeploying a tagged image — never :latest without an explicit pin.
Container PaaS options (Clever Cloud, Fly.io, Render) absorb registry and light orchestration if you prefer to skip VPS administration.
Document the exact docker run flags or compose file you use in production: port mapping, volume paths, environment file location. When the only person who knows the command leaves, a one-container setup becomes harder to recover than a plain systemd service if nothing is written down.
Beginner production mistakes
Avoid :latest without a pinned version: deploy becomes non-reproducible. Do not run the container as root: escape is more dangerous. Set memory limits: silent OOM kill can surprise you. Do not copy a dev compose to prod with exposed ports and weak passwords. Finally, do not confuse Docker with orchestration: Docker alone does not deliver multi-node HA — Kubernetes is another league.
The peak: Docker simplifies deploy, not architecture
Before stacking, ask: what must scale separately? If the answer is "nothing yet", one container is enough.
Decide and move forward without blind spots
Write a multi-stage Dockerfile with a non-root user, externalize the database and heavy files, and reserve Compose for services that truly co-locate. Wire CI to build and push the image, then pull a tagged version on the VPS. Compare Docker and PaaS hosts via the directory and the guide PaaS or server to place complexity elsewhere if needed.
Frequently asked questions
Does Docker replace a classic VPS?
No. Docker encapsulates the application; you still need a host machine. It standardizes the runtime environment without replacing infrastructure.
One container or docker-compose from day one?
One app container with managed database is often enough. Compose becomes useful if Redis or PostgreSQL run on the same machine as the app.
Where to store persistent data?
Docker volumes or managed services — not ephemeral filesystem alone, or you lose data on restart.
Docker on shared hosting?
Rarely available. Docker targets VPS, dedicated, or container PaaS.
Docker for application hosting is not collecting containers. It is one deployable artifact — then as many services as your architecture actually requires.