Why Choose Docker
Docker packages applications and their dependencies into portable container images, delivering true "build once, run anywhere" capability. Whether on a developer's laptop, a testing server, or a cloud host, the same image provides a consistent runtime environment, completely eliminating the classic "it works on my machine" problem. This environment consistency is the foundation for all subsequent automated deployment and upgrading.
Sources: cloud.tencent.com and dataintellect.com both point out that containerization ensures applications behave consistently across environments, greatly simplifying deployment and management.
Using Docker Compose to Orchestrate Multi-Container Applications
For projects containing multiple services (such as a web application, database, and cache), manually starting each container is tedious and error-prone. Docker Compose allows you to define and launch an entire application stack with a single command:
yaml
docker-compose.yml
version: '3.8' services: web: build: . ports: - "80:80" environment: - DATABASE_URL=postgres://user:password@db:5432/mydb restart: unless-stopped db: image: postgres:alpine environment: - POSTGRES_PASSWORD=password
Running docker compose up -d starts all services in the background. You can quickly check status and logs with docker compose ps and docker compose logs -f.
Sources: circleci.com and cnblogs.com.
Automated Version Management and CI/CD Integration
In a continuous delivery pipeline, manually specifying image version numbers is error-prone. A better approach is to automate semantic versioning (SemVer) based on commit messages:
- Default: increment the patch version (PATCH).
- If the commit message contains
bump: minor, increment the minor version (MINOR). - If it contains
bump: major, increment the major version (MAJOR).
The following GitHub Actions snippet shows how to implement this logic:
yaml
- name: Determine Version Bump Type id: determine-bump-type run: | echo "level=patch" >> $GITHUB_ENV if git log -1 --pretty=%B | grep -q 'bump: major'; then echo "level=major" >> $GITHUB_ENV elif git log -1 --pretty=%B | grep -q 'bump: minor'; then echo "level=minor" >> $GITHUB_ENV
The version level can then be passed to the build step, generating tags like ghcr.io/username/repo:1.2.3. Integrating tools like Trivy for image vulnerability scanning can also intercept known security issues before deployment.
Sources: dev.to and iamraghuveer.com.
Blue-Green Deployment: Achieving Zero-Downtime Switching
Blue-green deployment reduces risk by maintaining two identical environments: one (blue) is actively serving production traffic, while the other (green) sits idle. The new version is first deployed to the idle environment. Once verification passes, traffic is switched (for example, by modifying the Nginx upstream configuration) to direct users to the new environment. The entire process is seamless for users.
On a single VPS, you can leverage Docker's isolation and Nginx's dynamic reloading to implement this pattern:
- Start new containers on different host ports (e.g.,
3001for blue,3002for green). - Confirm the new container is ready via health checks.
- Modify the Nginx configuration to point the upstream from the old port to the new port, then execute
nginx -s reload(reload, not restart, to avoid disconnecting existing connections). - Stop and remove the old container.
bash
Start the green environment
docker compose -p green up -d
Health check
curl -sf http://localhost:3002/health
Switch the Nginx upstream
sed -i 's/3001/3002/g' /etc/nginx/sites-available/myapp.conf nginx -s reload
Clean up old containers
docker compose -p blue down
If the new environment encounters issues, rollback is equally simple: just point Nginx back to the old port and reload. The old containers are still running, ready to restore service immediately.
Sources: github.com, dev.to, and readyserver.sg.
Data Persistence: Docker Volume Practice
Containers are disposable, but business data cannot disappear with them. Docker Volumes provide a persistent storage solution independent of the container lifecycle, ideal for database files, user uploads, logs, and more.
- Create a named volume:
docker volume create myapp-data - Declare the volume in Compose:
yaml volumes: myapp-data: driver: local
- Mount it into containers:
yaml services: app: volumes: - myapp-data:/app/data
You can also use --volumes-from to conveniently back up and restore volume data:
bash
Back up the volume
docker run --rm --volumes-from myapp -v $(pwd)/backup:/backup ubuntu tar cvf /backup/backup.tar -C /app/data .
Restore the volume
docker run --rm --volumes-from myapp -v $(pwd)/backup:/backup ubuntu bash -c "cd /app/data && tar xvf /backup/backup.tar --strip 1"
Sources: semaphore.io and dev.to.
Reusable Deployment and Upgrade Process
Integrating the above technologies yields a complete practical workflow:
- Commit code: Use semantic commit messages (e.g.,
bump: minor) to trigger the CI/CD pipeline. - Build and version: The pipeline automatically builds the image, determines the version number based on the commit message, and pushes it to the container registry.
- Blue-green deployment: On the target server, a script automatically deploys the new version to the idle environment, performs health checks, and switches traffic once validated.
- Data protection: All stateful data is persisted via Docker volumes. An optional backup step before upgrading ensures data safety.
- Rollback readiness: The old environment is retained for a period of time. If the new version is abnormal, rollback can be completed in seconds.
This workflow is not only applicable to single-machine environments but can also be extended to multi-host clusters (combined with Docker Swarm or Kubernetes).
Sources: Synthesized from materials cited in this article.
Verification and Troubleshooting
After deployment, verify using the following methods:
- Check container status:
docker psordocker compose ps. - View application logs:
docker logs <container-name>ordocker compose logs -f. - Directly access the health endpoint:
curl http://your-domain/health. - Confirm correct volume mounting:
docker inspect <container-name>to check mount points.
Common issues and solutions:
| Issue | Possible Cause | Solution |
|---|---|---|
| Image build failure | Dockerfile error or missing dependencies | Check build context and base image version |
| Container exits immediately after startup | Missing environment variables or port conflicts | Use docker logs to view errors, verify .env file |
| Cannot access app after blue-green switch | Nginx not reloaded or incorrect upstream port | Run nginx -t to test configuration, manually nginx -s reload |
| Data not persisted | Volume not correctly mounted or anonymous volume used | Confirm volume definition and mount paths in docker-compose.yml |
Precautions
- Resource limits: When running two environments on a single machine, ensure the host has sufficient CPU and memory. You can limit container resources using
--memoryand--cpu-shares. - Database migrations: During blue-green deployment, the old and new versions may access the database simultaneously. It is recommended to use an "expand and contract" pattern: first add new columns while keeping old ones, then remove the old columns once the new version is stable.
- Image security: Always use official or trusted base images, and perform regular vulnerability scans (e.g., with Trivy).
- Network isolation: Production environments should use custom bridge networks and restrict unnecessary communication between containers.
With the methods described in this article, you can build a reliable, reusable Docker deployment and upgrade workflow without requiring complex orchestration tools. From environment consistency to zero-downtime switching, every step includes concrete commands and configurations you can reference. Start practicing in a testing environment and adjust the details according to your actual needs.