Application Deployment

Compose, Container Panels, and Update Management

Compiled by the VPSMap Editorial Team · Updated 2026-09-26 · 20-minute read · Plain-Text Version

When managing multi-container applications on a Linux VPS, directly using scattered docker run commands can easily cause forgotten parameters, confused network topology, and disastrous upgrades. Modern container delivery uses Docker Compose to define image tags, environment variables, persistent volumes, networking, and resource limits as Infrastructure as Code.

Focused on high availability and data security in production operations, this article explains standardized Compose directory organization, internal network isolation, tighter permissions for graphical management panels, and smooth update and emergency rollback procedures that account for database migrations. It also uses the console capabilities of major cloud providers such as BandwagonHost and DMIT to provide guidance on low-level troubleshooting and operational disaster recovery.


1. Architecture Standards: One Application Directory and Isolated State#

To ensure portability and cold migration in seconds, host-level container orchestration should follow the principle of one directory per application stack. It is recommended to standardize on /srv/stacks/<应用名> or /opt/stacks/<应用名> as the root path for isolation.

text
/opt/stacks/web-app/
├── compose.yaml          # 核心编排文件(Compose V2 规范)
├── .env                  # 敏感环境变量(权限 600,禁止提交至公开仓库)
├── config/               # 挂载至容器的应用专属静态配置文件
│   └── nginx.conf
└── data/                 # 宿主机绑定挂载的数据目录(非必要时优先使用命名卷)
    └── app-uploads/

Directory and Credential Management Guidelines#

  1. Updated Naming Conventions:现代 Compose 插件统一采用 compose.yaml(or compose.yml) as the default configuration file; the old top-level version: '3.8' 声明行。调用命令统一为 docker compose(with a space between the words), rather than the obsolete standalone binary docker-compose。
  2. 环境凭据隔离: Dynamic configuration values such as database passwords and API Tokens must be extracted into the same directory's .env file, with Compose automatically performing variable interpolation (${VARIABLE_NAME}). To protect the host, this file's permissions must be locked down:
    bash
    chmod 600 /opt/stacks/web-app/.env
    chown -R root:root /opt/stacks/web-app
  3. Version Control Boundaries:若使用 Git 追踪运维配置,应在仓库内提供 .env.example 模板,并在 .gitignore 中严格排除 .env and all that contain actual business state data/ 目录。

2. Production-Grade Orchestration Standards and Network Isolation#

directly map all container ports to the host's 0.0.0.0 is a serious network security risk. The Docker daemon directly modifies the system's iptables rules and inject DOCKER chain,Bypasses the System's UFW or firewalld Settings. Once a database or cache service is mapped externally, its port is directly exposed to the public internet.

以下是一套集成独立应用 Networking 、日志滚动上限、 resources 约束与反向代理预留的 Standard compose.yaml Template:

yaml
services:
  web:
    image: ghost:5-alpine
    container_name: production_blog_web
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      database__client: mysql
      database__connection__host: db
      database__connection__user: ghost
      database__connection__password: ${MYSQL_PASSWORD}
      database__connection__database: ghost_prod
      url: https://example.com
    volumes:
      - blog_content:/var/lib/ghost/content
    ports:
      # 严格限制仅监听宿主机本地环回接口,交由外部 Nginx 或 Caddy 处理 TLS 与反代
      - "127.0.0.1:2368:2368"
    networks:
      - frontend_net
      - backend_net
    deploy:
      resources:
        limits:
          cpus: '1.50'
          memory: 1024M
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "3"

  db:
    image: mariadb:10.11
    container_name: production_blog_db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ghost_prod
      MYSQL_USER: ghost
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    # 绝不发布 ports,仅在内部网络被同栈应用发现
    networks:
      - backend_net
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "2"

volumes:
  blog_content:
    name: blog_app_content
  db_data:
    name: blog_app_db_data

networks:
  frontend_net:
    driver: bridge
  backend_net:
    driver: bridge

Key Architectural Considerations#

  • Two-Layer Network Isolation:db The Container Joins Only backend_net, completely eliminating the possibility of communication with the public internet;web Containers use Compose's built-in DNS and the service name db 解析并建立连接。
  • Design to Prevent Disk Exhaustion: Docker's default logging driver allows container stdout/stderr logs to grow without limit. Explicitly declare in the configuration max-size and max-file can enforce log rotation and prevent low-specification VPS instances from going down because their disks fill up.
  • Pin Image Versions: Never use the following directly in production: :latest. Use stable images with explicit major versions or pinned build hashes (such as mariadb:10.11、alpine branch) to avoid pulling a build with breaking changes during an unexpected restart.

3. Deployment Verification and Common Command Workflows#

After setting up the directory structure, follow the standard sequence: “syntax check $\to$ image pull $\to$ background build $\to$ status audit”:

bash
# 1. 验证配置文件合法性与变量解析状态(不显示敏感数据)
docker compose config --quiet

# 2. 预先抓取所有编排中的镜像,降低上线中断时长
docker compose pull

# 3. 后台守护模式创建并启动容器
docker compose up -d

# 4. 检查服务健康状态与端口监听
docker compose ps

# 5. 持续观测业务输出以排查初始化异常
docker compose logs -f --tail=100 web

When Troubleshooting, Remember:docker compose config will .env all internal variables are fully expanded. When sharing debugging information with your team or a support ticket system, never paste the complete unsanitized configuration.


4. Choosing Visual Panels (Portainer / 1Panel) and Reducing Their Attack Surface#

Some teams add lightweight container management panels to simplify collaboration or monitoring. However, it is essential to recognize that:All Management Panels Connected to Docker Are Essentially Privileged Processes。

Docker Socket Security Boundaries#

Nearly all visual management panels, such as Portainer, need to mount the host's Docker communication socket: -v /var/run/docker.sock:/var/run/docker.sock

Mounting This Socket into a Container Grants It the Same Host-Level root 等同的控制权限。一旦面板的前端密码外泄或存在远程代码执行漏洞,攻击者可以直接 Passed 该套接字创建特权容器挂载宿主机根目录,直接接管整个系统。

Panel Hardening Guidelines#

  1. Do Not Expose to the Public Internet: 切勿将面板端口(例如 Portainer 的 9443 或 1Panel 端口)直接开放至 public network 。生产环境下应强制绑定为本地监听(127.0.0.1), with access provided only through local port forwarding over a secure SSH-key-based tunnel:
    bash
    # 在本地工作电脑建立安全隧道,将远程端口转发至本地
    ssh -N -L 9443:127.0.0.1:9443 user@vps-ip -i ~/.ssh/id_ed25519
    Then open in your local browser https://127.0.0.1:9443 access the panel.
  2. Avoid Configuration Drift from Two-Way Management: State conflicts can easily arise between a graphical panel and local YAML files. If you change a container's mapped ports or environment variables directly in the panel's WebUI while the local compose.yaml 未作同步,当下一次 Passed 终端执行 docker compose up -d any temporary changes made in the panel will be completely erased. Best Practices: Use the panel only as a Read-only monitor or log-audit tool. Keep the version-control repository and terminal scripts as the Single Source of Truth for all create, read, update, and delete operations.

5. Graceful Image Updates, Data Migration, and Rollback Control#

Many beginners assume that updating a container only means pulling the latest image and restarting it. For persistent business applications, this can easily interrupt database Schema migrations or corrupt data.

Standard Four-Step Rolling Update Method#

text
[1. 业务静态化与状态备份] ──> [2. 拉取新镜像] ──> [3. 差异化重建容器] ──> [4. 健康校验]

Step One: Perform Cold Backups of the Application and Database#

Before switching to a new image, create a logical dump of the data rather than relying solely on a host disk snapshot.

bash
# 进入部署目录
cd /opt/stacks/web-app

# 逻辑导出当前数据库数据并压缩
docker compose exec db mariadb-dump -u ghost -p"${MYSQL_PASSWORD}" ghost_prod | gzip > backup_$(date +%F_%H%M).sql.gz

# 备份持久化配置与上传目录
tar -czvf app_data_$(date +%F).tar.gz config/ data/

Step Two: Update Images and Replace Containers#

修改 compose.yaml the target service (such as web) version tag, then perform a differential rebuild:

bash
# 仅拉取更新的镜像层
docker compose pull web

# 触发依赖项计算,仅重新创建发生了配置或镜像变更的容器
docker compose up -d web

# 清理不再使用的孤儿容器与旧版本镜像
docker compose ps
docker image prune -f

docker compose up -d can update the stack in place without disrupting a separate database service that remains healthy.

The Fatal Rollback Misconception: The Schema Trap#

When a new service version fails to start correctly and an urgent rollback is needed, many people simply change compose.yaml change the image version back to the old tag there and run up -d。

Critical Hidden Risk: many web applications, such as Nextcloud, WordPress, and Ghost, automatically perform irreversible database schema migrations when a new version first starts, including dropping columns, renaming fields, or converting character sets. Older application code cannot interpret tables modified by the newer version, so errors and crashes continue even if the image rollback succeeds.

Complete Rollback Procedure:

  1. Stop the Application Containers Immediately:docker compose stop web
  2. 使用先前导出的备份 Reset Databases :
    bash
    # 清空并恢复数据库
    gunzip < backup_YYYY-MM-DD.sql.gz | docker compose exec -T db mariadb -u ghost -p"${MYSQL_PASSWORD}" ghost_prod
  3. 修改 compose.yaml 恢复旧版 image 标签,并恢复对应的旧版配置文件。
  4. Rebuild the Previous Container Version:docker compose up -d web

Warning: Avoid docker compose down -v
docker compose down only stops and removes containers and bridge networks; however, if it includes -v(or --volumes) parameter, Docker willerase directly compose.yaml Named Volumes declared in. Unless you intend to destroy the environment completely, never use the following option on a production server: -v command.


6. Coordinating Underlying Cloud Networking and Emergency Operations#

Container orchestration runs on the underlying cloud server. Network interface problems, firewall blocks, or lost credentials can directly disrupt container lifecycles. Making effective use of the host's infrastructure features is the final line of defense against operational disasters.

Troubleshooting Firewall and Underlying Connectivity Failures#

Because Docker manages NAT tables and bridges itself (docker0 / br-xxxx), routing-table conflicts may occasionally occur after containers restart, even making the host's SSH daemon unreachable.

  • DMIT Emergency Access: DMIT's instance control panel provides a dedicated Web Console 入口。当 SSH 端口因 Docker 容器端口占用或 Networking 策略封锁无法连入时,应直接 Passed 面板 Console 发送硬件中断信号或直接登录排查。此外,DMIT 系统为了防范暴力破解,默认关闭了远程 root password login, strongly favoring SSH-key access; if you need to replace or inject public keys in bulk through the panel's Access menu, after the operationRestart the Instance (Reboot) as Instructed by the Panel, after which the underlying cloud-init and authentication mechanisms take effect.
  • In-Depth BandwagonHost KiwiVM Troubleshooting: BandwagonHost assigns each VPS a separate KiwiVM panel. Note that the KiwiVM panel password, client area account password, and the system's internal root 密码完全独立。当宿主机 Networking 发生故障时,可使用 KiwiVM 内置的 Interactive Console Bypass the regular network stack to access a TTY terminal directly, then enter root password to take control of the server. If incorrect container networking disables the entire network, the panel can even initiate an OS reinstall or snapshot restore.

自动化续费与数据保全边界#

Container orchestration allows applications to be rebuilt quickly, but does not replace cross-node disaster recovery for persistent data. If an invoice becomes overdue or data center policies change, all data volumes on the host may be erased directly.

  • Billing and Retention Windows:
    • BandwagonHost: The provider does not automatically charge a linked credit card or PayPal account without your involvement. If renewal is enabled, an invoice is generated 7 days before expiration. If the prepaid account balance is sufficient, the system deducts it to renew the service; otherwise, an administrator must complete checkout manually.
    • DMIT: Monthly renewal invoices are also generated 7 days before expiration. If late payment causes suspension, the data center usually provides only 3 daysof data retention grace time; once it expires, automated processes permanently destroy the instance and its mounted Docker data, with no recovery.
  • Cross-Data-Center Migration Restrictions: although BandwagonHost offers convenient cross-region data center migration, if prohibited outbound activity or abuse causes an instance's IP to be blacklisted, the system strictly restricts migration in the underlying panel. If a misconfigured container is compromised and used to scan external networks, consequences range from IP blocking and service disruption to loss of migration and recovery privileges.
  • Minimum Off-Site Disaster Recovery Requirements: Regardless of the underlying provider, persistent directories (Volumes) and database Dump backups must be encrypted and uploaded outside the host on a daily schedule using object storage clients, such as AWS S3, R2, or Backblaze B2. Never keep all your eggs in one instance's virtual disk.

7. Production Handover Checklist#

After any Docker Compose deployment or major-version update, complete the handover audit using the following steps:

Validation CategoryKey ChecksVerification Method / Expected Behavior
Network IsolationDatabase/Middleware Ports Are Not Exposed to the Public InternetRun on the Host ss -tulpn | grep -E '3306|5432|6379' No External Listener
Privilege Review业务容器禁止使用特权模式compose.yaml 中无 privileged: true, with no unnecessary host mounts
resources 限制Memory and CPU Hard Limits Are Configured and EffectiveAfter startup, use docker stats --no-stream Verify That Container Memory Limits Have Taken Effect
Log Rotation日志不会撑爆宿主机磁盘Check that every service declares max-size and max-file Restrictions
Automatic Startup After a Power OutageAutomatic Recovery After a Host RebootService Configuration restart: unless-stopped, after restarting docker ps Shows Up
备份离线异地备份策略与备份校验Verify That Automated SQL Backup Files Are Nonempty and Can Be Imported into a Test Environment
Emergency Access ChannelOperations Console Available at All TimesVerify the Provider's Console Login Credentials in Advance to Ensure Low-Level Troubleshooting Access During a Network Outage