Containerizing Your DTC Site: Docker Config, Image Optimization, Orchestration, and Resource Limits

The goal of Docker for a DTC site isn't to move to K8s. It's to turn "it runs on this server" into "it runs on any machine." The bar: keep production images under 200MB, use multi-stage builds, declare CPU and memory limits explicitly on every container, and orchestrate a single VPS with docker compose. The counterintuitive part: bloated images almost never come from too much code. They come from leaving build tools, npm cache, and source code inside the runtime image. Together those three can balloon an image from 200MB to 1.5GB.

独立站Docker镜像:从1.2GB臃肿镜像到180MB生产镜像Independent-site Docker: from 1.2GB bloated image to 180MB prod 反直觉:镜像大不是因为代码多,而是因为把构建工具和源码全塞进了运行层Counter-intuitive: bloat comes from build tools shipped into runtime 错误做法:单阶段构建Wrong: single-stage build 正确做法:多阶段构建Right: multi-stage build Ubuntu基础镜像 500MBUbuntu base 500MB Node/PHP 全量 200MBFull runtime 200MB 源码 + node_modules 300MBSource + node_modules 300MB 编译工具链 200MBBuild toolchain 200MB alpine 基础 5MBalpine base 5MB 运行时 40MBRuntime 40MB 系统库 20MBSystem libs 20MB 应用代码 30MBApp code 30MB 静态资源 80MBAssets 80MB PHP扩展/依赖 50MBPHP ext 50MB 合计 ≈ 225MBTotal ≈ 225MB 资源限制:CPU 1至2核、内存512MB至1GB是独立站容器的甜蜜点;不设限制会把宿主机内存吃光Limits: 1-2 CPU, 512MB-1GB RAM; without limits host starves 编排选择:单台VPS用docker compose即可,3台以上再考虑Swarm;K8s对独立站是过度设计Compose for 1 box, Swarm for 3+, K8s overkill
Single-stage vs multi-stage build: image size comparison

Containerizing Your DTC Site: Docker Config, Image Optimization, Orchestration, and Resource Limits

Bottom line first: The goal of Docker for a DTC site isn’t to move to K8s. It’s to turn “it runs on this server” into “it runs on any machine.” The bar: keep production images under 200MB, use multi-stage builds, declare CPU and memory limits explicitly on every container, and orchestrate a single VPS with docker compose. The counterintuitive part: bloated images almost never come from too much code. They come from leaving build tools, npm cache, and source code inside the runtime image. Together those three can balloon an image from 200MB to 1.5GB.

1. Why containerize a DTC site instead of installing directly on the server

You can install PHP, Nginx, and MySQL straight on a VPS and it will run. But three problems surface within six months. First, a new hire spends two days setting up the environment. Second, when you want to migrate from Alibaba Cloud to Tencent Cloud, you reinstall every dependency. Third, when a security patch drops, you’re afraid to touch the production version. Docker packages the app and its dependencies into one immutable image, and those are the three problems it solves.

One test: if your deployment doc says “first apt install php8.3-fpm, then install these 12 extensions, then change these 8 php.ini settings,” you need containerization. Put those 12 extensions and 8 settings in a Dockerfile, and from then on any machine can reproduce it with one docker run. For how CDN and static assets work together, see CDN global acceleration.

Deployment options Environment setup cost Migrating servers Rollback speed Best for
Direct install on VPS 2 person-days Reinstall every dependency Snapshots, 30 minutes Prototype stage
Shell script setup 0.5 person-days Run the script, still drift 15 minutes Single site, 1 year
Docker image 5 minutes Pull image and run Switch image tag, 3 minutes Production, multi-environment
Docker Compose orchestration 5 minutes Copy the yaml and run One-click rollback App + database + queue

2. How to write a Dockerfile that isn’t bloated

The most common Dockerfile mistake on DTC sites is single-stage builds: FROM ubuntu, then install PHP, install Composer, composer install, COPY the source in, expose the port. Images built this way usually exceed 1GB because they include build tools, npm cache, and the .git directory. The right approach is a multi-stage build: stage one uses a builder image to compile the frontend and install composer dependencies; stage two copies only the build output and runtime into an alpine image.

A few hard rules: use alpine as the base image, not ubuntu. Your .dockerignore must exclude node_modules, .git, .env, and upload directories. Add –no-dev and –no-cache to npm/composer install. Run as a non-root user. Image size targets: keep a pure PHP/Node app under 200MB, and under 400MB if it ships static assets. For image scanning and security baselines, see Backup and recovery drills for your site.

A real-world case: a team found their image was 1.4GB six months after launch. Digging layer by layer with docker history, they found composer install was missing –no-dev, so phpunit and a pile of dev dependencies had been baked into the production image. COPY . . had also pulled in the .git directory. After switching to a multi-stage build, with dependencies installed in the builder stage and only the vendor and public directories copied into the runtime stage, the image dropped to 180MB and deploy time went from 3 minutes to 40 seconds.

Tier Common mistakes Optimized approach Size saved
Base image FROM ubuntu:22.04 FROM php:8.3-fpm-alpine About 400MB
System dependencies apt-get install build-essential Multi-stage build, installed in the builder stage About 200MB
PHP dependencies composer install composer install –no-dev –no-cache About 80MB
Frontend dependencies COPY . . npm ci && npm run build in the builder stage About 300MB
Source directory COPY . /var/www COPY –from=builder /app/public ./public About 100MB
Runtime user Running as root RUN adduser -D app Security gain, no size change

3. How to orchestrate a standalone site with docker compose

A typical standalone site runs four containers: Nginx as reverse proxy, a PHP-FPM or Node app, MySQL/PostgreSQL, and Redis. Wire the four services together in a single docker-compose.yml, mount a data volume to persist MySQL data, expose only ports 80 and 443 on Nginx, and keep the other services on the internal network. Never expose the MySQL port to the public internet.

A config snippet you can actually use: Nginx depends_on php, php depends_on db and redis; the health check runs curl -fsS http://localhost/health every 30 seconds; the restart policy is unless-stopped across the board. For backups, mount the MySQL data volume to a backup directory on the host on a schedule, then sync it offsite with rsync. For monitoring to go with multi-container orchestration, see Site monitoring and alerts.

Example: a 4-core, 8GB Alibaba Cloud lightweight server running three containers: WordPress, MySQL, and Redis. The MySQL container mounts a named volume for data, Nginx maps ports 80 and 443 to the host, and the WordPress container exposes no ports and stays on the internal network. After a host reboot, docker compose up -d brings all three services back up automatically. Nothing needs to be started by hand after logging in. That is the plainest value of compose.

Services Base image CPU limit Memory limit Persistent
nginx nginx:1.27-alpine 0.5 core 128MB No
php-fpm / node php:8.3-fpm-alpine 1 to 2 cores 512MB to 1GB No (mount a volume for uploads)
mysql mysql:8.4 1 to 2 cores 1 to 2GB Yes (data directory)
redis redis:7-alpine 0.5 core 256MB Yes (AOF persistence)
cron/queue Same as the app image 0.5 core 256MB No

4. Resource limits: an unbounded container can take down the whole machine

Many people use Docker for convenience and skip –cpus and –memory at startup. The result: one slow query hits PHP-FPM, memory climbs from 512MB to 4GB, and MySQL on the same machine gets squeezed into an OOM kill. Every container must declare resource limits explicitly. This is not optional.

Resource quotas for a standalone site: 1 to 2 CPU cores and 512MB to 1GB memory for the app container; size the MySQL container to your data, and 1 core and 1GB is enough for under 10GB; 0.5 core and 128 to 256MB each for Nginx and Redis. Leave 20% of the host for the Docker daemon and the system. One rule of thumb: allocate 80% of host memory to containers and keep 20% for the system. Go over that ratio and it is time to add a machine. Allocate CPU by peak, not by average. Standalone site traffic peaks on weekdays and drops at night, so sizing CPU by the average means queues at peak.

Site stage Recommended setup CPU allocation Memory allocation Monthly visits
Early validation 1 x 2-core 2GB VPS App 1 core + DB 0.5 core App 512MB + DB 1GB <50K
Steady growth 1 x 4-core 8GB VPS App 2 cores + DB 1.5 cores App 2GB + DB 4GB 50K to 300K
Multilingual peak 2 x 4-core 8GB App 2 cores each 2GB each, DB 4GB separate 300K to 1M
Independence Day peak Scale the app out to 3 to 4 machines 2 cores each 2GB each Promo and trade show traffic

5. When to move to Swarm or K8s

The counterintuitive conclusion: 90% of standalone sites do not need K8s. docker compose runs 4 to 8 containers on a single VPS, which is enough for a B2B site with under 100K daily visits. Only when one of these three signals appears should you consider moving up to Swarm: you need to run the app across two or more VPSs, you need blue-green deploys that shift traffic automatically, or you need health checks that restart dead containers.

The test is simple: if your docker-compose.yml has more than 30 services, or more than 5 people maintain the environment at once, then consider an orchestration system. Otherwise compose is the best answer, and the effort you would spend maintaining K8s pays off more in content and SEO.

The barrier with K8s is that you need someone dedicated to etcd, network plugins and the Ingress controller, and troubleshooting alone eats a day a week. For a small team, that time pays off more in content and SEO. If you really do need multi-instance deployment later, see the load balancing design in Load balancing and multi-instance deployment.

Orchestration tool Learning curve Maintenance cost Node count When to choose it
docker compose 1 to 2 days Extremely low 1 First choice for 90% of standalone sites
docker swarm 3 to 5 days Low 2 to 10 Cross-machine orchestration and rolling updates
K8s 2-4 weeks High 5 or more Multiple teams, multiple services, autoscaling
Managed K8s from a cloud provider 1 to 2 weeks Medium 10 or more You already have a dedicated DevOps engineer

FAQ

Is Docker slower than installing directly on a server for a standalone site?
The container itself costs you less than 2% in performance, which is barely noticeable. What actually slows things down is cramming MySQL into the container, or skipping the data volume so writes land on the overlay layer. The right approach: give MySQL and Redis containers named volumes for persistence, and keep the app stateless. That’s cleaner than installing directly on the server, with the same performance.
What’s a reasonable image size?
A pure PHP/Node runtime image should stay under 200MB, and under 400MB if it includes frontend build output. Anything over 1GB almost certainly means you’ve baked in build tools or source code. Check the size with docker images, then use docker history to see which layer is the biggest. The usual culprits: node_modules not removed in a multi-stage build, or apt-get install without a follow-up apt-get clean.
Can docker compose handle 100,000 daily visits?
A single 4-core, 8GB VPS running compose handles up to 100,000 daily visits fine. The bottleneck is usually MySQL, not Nginx. Past 200,000 daily visits, move MySQL to its own machine first, then run the app with compose across two VPSs. Only when you hit 500,000 daily visits should you consider load balancing and multiple instances. Don’t buy complexity up front for traffic that doesn’t exist yet.
How do I handle logs and uploaded files inside containers?
Don’t write logs into the container layer. Mount them to a host directory, or output to stdout and let docker’s json-file driver collect them. Uploaded files (inquiry attachments, customer-uploaded PDFs) must be mounted to a host volume, or they’re all lost when the container is rebuilt. I recommend mounting the uploads directory as its own volume and rsyncing it to object storage on a schedule. For the full approach to database dumps and backups, see Backup and recovery drills for your site.
When should I use K8s, and when should I stick with compose?
Wait until two of these three signals appear before considering K8s: your app instances need to span more than three machines, you have a clear need for auto-scaling, and someone on the team can maintain K8s and troubleshoot it day to day. Until then, compose is more than enough. The hidden cost of K8s is troubleshooting etcd, CNI, Ingress, certificate management, and a pile of other components, which eats at least one person-day every week.
Will an alpine image be missing dependencies and fail to run?
Yes. The most common trap is that PHP extensions need build-base to compile but not to run. Multi-stage builds solve this: install build-base and compile-time dependencies like libpng-dev in the builder stage, and only runtime libraries like libpng in the final stage. If that’s too much hassle, just use the official php:8.3-fpm-alpine image, which already ships with common extensions preinstalled. If you hit an undefined symbol, install whatever’s missing.

Final thoughts

For a standalone site, 80% of the value of containerization is environment consistency, and 20% is orchestration. Get the Dockerfile right, shrink the image to 200MB, and manage your four services well in a compose file, and you’ve solved the most painful problem in a standalone site’s tech stack: environment drift. Don’t chase K8s from the start. That’s a whole other full-time job.

Rollout path: Week 1, write a multi-stage Dockerfile and shrink the image from 1GB to 200MB. Week 2, orchestrate Nginx + app + MySQL + Redis with Compose and mount the data volumes properly. Week 3, hook the CI/CD pipeline up to an image registry so pushing code builds the image automatically. Finish these three steps and migrating servers drops from two days to two hours. For how this connects to CDN and monitoring, see CDN global acceleration. In day-to-day operations, run docker system df once a week to clear dangling images, and your disk won’t slowly fill up with images you no longer use.

One line worth remembering: the first step in containerizing isn’t moving to K8s, it’s getting to the point where you can run your site on any clean server with a single command.

Crater
Crater Crater experts

Official Organization · Crater

Crater is a go-global services company specializing in WordPress standalone site development + Google SEO + GEO full-funnel growth, serving sectors including smart hardware, industrial equipment, SaaS, and new energy.

Product CapabilitiesMethodology WhitepaperIndustry Report
965articles shared
78knowledge base articles