The whole process of Docker deployment: Dockerfile and Docker Compose orchestration

📂 Stage: Stage 6 - Online Deployment (Production) 🔗 Related chapters: Gunicorn and Nginx · Environment variables and security configuration

The local development goes smoothly, but as soon as it is thrown on the server, various errors are reported: the Python version is wrong, PostgreSQL cannot be connected, Redis is not installed, the path configuration is completely messed up... This kind of "environment inconsistency hell" is almost a must-have for every Flask project going online.

Docker is a good remedy for this kind of problem - it packages applications, dependencies, and runtime environments into a standardized "image", and then generates isolated "containers" through the image. No matter which machine it is run on, the environment in the container is exactly the same. This article will help you write a production-ready Dockerfile from scratch, then use Docker Compose to orchestrate a complete service of Flask + PostgreSQL + Redis + Nginx, and finally complete one-click deployment on the cloud server.


1. Write an efficient and safe Dockerfile

Dockerfile is an "image building guide", and each instruction will generate an image layer. A reasonable layering order can not only greatly improve the build speed, but also improve security. We will build a Flask application step by step based on the lightweight version of Python 3.11.

Complete Dockerfile

# 1. 选择基础镜像:python:3.11-slim 只保留运行必须的系统库,体积小、更适合生产
FROM python:3.11-slim

# 2. 设置工作目录:所有后续操作都在 /app 下进行,避免路径混乱
WORKDIR /app

# 3. 先复制依赖文件,再安装依赖——这是缓存优化的关键!
#    只要 requirements.txt 不变,Docker 就会复用缓存的 pip install 层,
#    不用每次改一行代码都重新下载整个依赖包。
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 4. 最后复制应用代码,代码变更频率最高,放在最下层可以减少重建层
COPY . .

# 5. 安全加固:创建一个非 root 用户,并修改文件权限
RUN useradd --create-home appuser && \
    chown -R appuser:appuser /app
USER appuser

# 6. 声明容器监听端口(仅作文档说明,实际映射通过 docker run 或 Compose 实现)
EXPOSE 8000

# 7. 启动命令:使用 Gunicorn 作为生产级 WSGI 服务器,绝不要用 flask run
CMD ["gunicorn", "-c", "gunicorn.conf.py", "app:create_app()"]

Dismantling of layered ideas:

  • Base Image:python:3.11-slimCompared with the full version, the compilation tools and documentation are removed, and the size is smaller and the attack surface is smaller.
  • Dependent Installation: ExploitCOPY requirements.txt .andRUN pip install ...Form an independent cache layer. Routine modifications to application code will not trigger dependency layer reconstruction, and build times are reduced from minutes to seconds.
  • Non-root operation: The default container has root permissions. Once compromised, the host may be endangered. Switch to normal userappuserIt is the most basic security practice.
  • Start Command: PassedgunicornStart the Flask application factory, which is suitable for multi-process request processing in production environments.

Supporting requirements.txt

This file lists the Flask family bucket and commonly used database drivers, asynchronous task libraries, etc. to ensure that all dependencies are available when the project is running:

# requirements.txt
Flask>=3.0.0
gunicorn>=21.0.0
flask-sqlalchemy>=3.1.0
flask-login>=0.6.3
flask-wtf>=1.2.0
flask-migrate>=4.0.0
flask-cors>=4.0.0
psycopg2-binary>=2.9.0
redis>=5.0.0
python-dotenv>=1.0.0
email-validator>=2.0.0
Pillow>=10.0.0
celery>=5.3.0

💡 Tips:psycopg2-binaryIt is a precompiled version and does not require installing the PostgreSQL development library in the container, making it very suitable for Docker scenarios.


2. Use Docker Compose to orchestrate multiple containers

In a production environment, a Flask application often requires multiple services such as database, cache, reverse proxy, etc. Manualdocker runStarting one by one is not only tedious, but also difficult to manage dependencies and networks. Docker Compose makes all this possible with just one YAML file that defines how all containers are built, environment variables, data volumes, health checks, and startup sequences.

2.1 Local development version: hot reloading, fast debugging

When developing we need:

  • Automatically reload after modifying the code, no need to restart the container;
  • Environment variables can be written directly in the Compose file to facilitate quick startup;
  • Database and Redis have data persistence, but allow cleaning at any time.
# docker-compose.yml
version: "3.9"

services:
  # Flask Web 服务
  web:
    build: .                     # 基于当前目录的 Dockerfile 构建
    container_name: daoman_web
    ports:
      - "8000:8000"
    environment:                 # 开发环境直接写入变量
      - FLASK_ENV=development
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/daoman
      - REDIS_URL=redis://redis:6379/0
    volumes:
      - ./app:/app               # 挂载本地代码,实现热重载
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    command: flask run --host=0.0.0.0  # 覆盖 Dockerfile CMD,使用开发服务器

  # PostgreSQL 数据库
  db:
    image: postgres:16-alpine
    container_name: daoman_db
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: daoman
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  # Redis 缓存/消息队列
  redis:
    image: redis:7-alpine
    container_name: daoman_redis

  # Celery 异步任务 Worker
  celery:
    build: .
    container_name: daoman_celery
    command: celery -A app.celery_app worker --loglevel=info
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/daoman
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis

volumes:
  postgres_data:   # Docker 管理的卷,数据持久化

Key design points:

  • Code Mount:volumesconvert local./appThe directory is mapped to the container, and any code changes can be refreshed immediately without rebuilding the image.
  • HEALTH CHECK:depends_oncombinecondition: service_healthy, make sure the database is completely ready before starting the web service to avoid connection failure.
  • Development Server:command: flask runConvenient for debugging, but actual production needs to be replaced back to Gunicorn.

2.2 Production environment version: safe, stable and durable

The biggest difference between production deployment and development is:

  • Use pre-built images instead of on-sitebuild; -Environment variables are placed separately.env.productionFiles are never submitted to the code repository;
  • All containers openrestart: alwaysEnsure service self-healing;
  • Join Nginx reverse proxy to process static files and SSL certificates;
  • Configure health checks for critical services.
# docker-compose.prod.yml
version: "3.9"

services:
  web:
    image: daoman/flask:latest         # 从镜像仓库拉取,不再本地构建
    container_name: daoman_web
    restart: always                    # 进程崩溃或宿主机重启后自动拉起
    env_file:
      - .env.production                # 敏感信息放到单独文件
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  db:
    image: postgres:16-alpine
    restart: always
    volumes:
      - postgres_prod:/var/lib/postgresql/data
    env_file:
      - .env.production
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: always
    command: redis-server --appendonly yes   # 开启 AOF 持久化,防止数据丢失
    volumes:
      - redis_prod:/data

  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - static_files:/var/www/static        # 共享静态文件卷
    depends_on:
      - web

volumes:
  postgres_prod:
  redis_prod:
  static_files:

Production configuration points:

  • restart: always: No matter what the reason is for exiting, Docker will automatically restart the container and cooperate with the health check to quickly restore the service.
  • Sensitive information external:.env.productionThe file usually contains database passwords, keys, etc., via.gitignoreEliminate the risk of leakage.
  • HEALTH CHECK: The web service is accessed regularly/healthinterface (you need to simply implement this endpoint in your application) to confirm that the service is in a normal state.
  • Data persistence: Database and Redis data are stored in named volumes, and the data will not be lost even if the container is deleted.

3. One-click build and deployment process

Local development

Open the project root directory and execute the following commands to start the complete development environment:

# 启动所有服务(-d 表示后台运行)
docker-compose up -d

# 实时查看 Web 服务日志
docker-compose logs -f web

# 修改代码后自动热重载,刷新浏览器就能看到效果

# 停止所有服务(数据保留在卷中)
docker-compose down

# 停止并彻底清除所有数据(清理开发环境)
docker-compose down -v

Cloud server production deployment

Prerequisite: Docker and Docker Compose have been installed on the server and exist in the project root directorydocker-compose.prod.yml.env.productionas well asnginx/Configuration directory.

# 1. 拉取最新镜像(如果使用 Docker Hub)
docker-compose -f docker-compose.prod.yml pull

# 2. 首次启动所有服务
docker-compose -f docker-compose.prod.yml up -d

# 3. 如果只是更新了 Web 镜像,可以单独重建并重启 Web 服务(不影响数据库)
docker-compose -f docker-compose.prod.yml up -d --no-deps --build web

# 4. 进入 Web 容器进行调试或数据库迁移
docker exec -it daoman_web /bin/sh
# 进入后执行 flask db upgrade 等命令

🚀 Common Tips: Use--no-depsWhen a single service is restarted using parameters, the databases, Redis, etc. it depends on will not be restarted, thereby achieving zero-downtime rolling updates.


4. Review of Key Best Practices

  1. Hierarchical cache optimization: Dockerfile Lieutenant GeneralCOPY requirements.txtPlace it before application code and take advantage of layer caching to speed up builds.
  2. Security reduction: All containers must be run as non-root users to reduce the potential attack surface.
  3. Data persistence: Database, Redis, static files, uploaded files, etc. are all mounted to the Docker volume or host directory to avoid data loss when the container is destroyed.
  4. Full health check coverage: The production environment configures healthcheck for all services to ensure reliable startup of the dependency chain.
  5. Sensitive Information Isolation: Production environment variables, database passwords, etc. must be placed in.env.productionFiles are never submitted along with the code.
  6. Lightweight Image: Basic image is preferred-slimor-alpineversion, which can not only reduce the size but also reduce security risks.

🔗 Extended reading

Now, you can apply this configuration directly to your Flask project and say goodbye to the trouble of "environment inconsistency" completely.