Skip to content

guogin/python-dev-env

Repository files navigation

用Podman搭建Python开发环境

📋 目录

  1. 问题背景
  2. 整体框架设计
  3. 安装Podman
  4. 创建项目目录结构
  5. 构建Python开发容器
  6. 配置PostgreSQL容器
  7. 验证环境
  8. 配置VS Code远程调试

问题背景

假设你有一台Mac,系统默认搭载Python 3.9.6。现在你需要搭建一个Python 3.12的开发环境,但又担心安装新版本会影响系统工具对默认Python的依赖关系。

又或者,你需要同时维护多个项目,每个项目依赖不同的Python版本,日常工作需要频繁在这些项目之间切换。

面对这类问题,全局安装Python显然不是理想方案。解决方法有很多,比如pyenv、conda、uv等工具。本文提供另一种思路——使用容器来搭建开发环境

整体框架设计

宿主机 (Mac或Windows或Linux)
├── /Users/<username>/Depots/my-project (源代码目录)
├── 容器1: Python开发环境 (Ubuntu 24.04)
│   ├── Python工具链
│   ├── Jupyter Notebook
│   ├── 挂载源代码目录
│   ├── 暴露端口: 8888(Jupyter Notebook), 5678(调试端口)
│   └── 连接到Postgres容器
└── 容器2: PostgreSQL数据库
    └── 暴露端口: 5432

需要说明的是,容器并非解决此类问题的唯一方案,也未必是最优方案。这里只是提供一种思路,因为容器具有以下独特优势:

  • 环境一致性:轻松保持开发、测试、生产环境的一致性
  • 高度隔离性:项目间完全独立。当你切换环境时,可以同时切换Python版本、依赖库、数据库,甚至包括HOME目录下的配置文件(如.config
  • 系统环境保护:无需在宿主机上安装大量软件。容器相当于一个独立的操作系统,删除容器即可清除所有安装的软件
  • 环境可复现:更换电脑时无需重新安装大量软件,只需重新构建容器即可获得完全相同的环境,甚至可以直接导出导入容器镜像
  • 团队协作优势:无需手把手帮同事配置环境,也彻底解决了"在我电脑上能跑"的经典问题

1. 安装Podman

Mac系统:

# 使用Homebrew安装
brew install podman podman-compose

# 初始化Podman虚拟机(Mac需要Linux VM运行容器)
podman machine init --cpus 4 --memory 4096 --disk-size 50

# 启动Podman虚拟机
podman machine start

# 验证安装
podman --version

Windows 11系统:

# 方法1:使用winget(推荐)
winget install RedHat.Podman

# 方法2:从官网下载安装器
# https://github.com/containers/podman/releases

# 即使安装了Podman Desktop,命令行使用podman-compose还需要:
pip install podman-compose

# 安装后,初始化Podman虚拟机
podman machine init --cpus 4 --memory 4096 --disk-size 50

# 启动Podman虚拟机
podman machine start

# 验证安装
podman --version

说明

  • Podman是Docker的替代方案,开源免费,且无需root权限即可运行
  • 分配4核CPU和4GB内存可确保开发环境流畅运行
  • Windows系统需要提前启用WSL 2(推荐)或Hyper-V作为虚拟化技术的底层实现
  • Windows系统还需要安装全局Python环境来运行podman-compose

2. 创建项目目录结构

在工作目录下执行以下命令(Mac使用Terminal,Windows使用PowerShell):

# 创建项目根目录
mkdir python-dev-env
cd python-dev-env

# 创建子目录
mkdir -p src
mkdir -p postgres-data
mkdir -p notebooks
mkdir -p data
mkdir -p .vscode

# 创建必要的文件
touch Dockerfile
touch docker-compose.yml
touch start.sh
touch requirements.txt
touch src/test_db.py
touch src/debug_example.py
touch notebooks/01_database_test.ipynb
touch .vscode/launch.json

目录结构

python-dev-env/
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── start.sh                 # 启动脚本
├── src/                     # Python脚本
│   ├── test_db.py
│   └── debug_example.py
├── notebooks/               # Jupyter notebooks
│   └── 01_database_test.ipynb
├── data/                    # 数据文件
├── postgres-data/           # PostgreSQL数据
└── .vscode/
    └── launch.json

3. 构建Python开发容器

3.1 创建Dockerfile

编辑Dockerfile文件,添加以下内容:

# 基于Ubuntu 24.04
FROM ubuntu:24.04

# 设置环境变量,避免交互式提示
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
ENV TZ=Asia/Shanghai

# 更新系统并安装必要的工具和Python 3.12
RUN apt-get update && apt-get install -y \
    software-properties-common \
    curl \
    wget \
    git \
    vim \
    build-essential \
    libpq-dev \
    tzdata \
    nodejs \
    npm \
    python3.12 \
    python3.12-dev \
    python3.12-venv \
    python3-pip \
    python3-pip-whl \
    && rm -rf /var/lib/apt/lists/*

# 配置系统时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
    echo $TZ > /etc/timezone

# 设置Python 3.12为默认版本
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 && \
    update-alternatives --install /usr/bin/python python /usr/bin/python3.12 1

# 创建工作目录
WORKDIR /workspace

# 复制requirements.txt并安装依赖
COPY requirements.txt .

# 使用系统pip安装Python包(避免升级pip本身导致冲突)
RUN python3 -m pip install --break-system-packages -r requirements.txt

# 安装debugpy(VS Code远程调试需要)
RUN python3 -m pip install --break-system-packages debugpy

# 配置Jupyter
# 创建Jupyter配置目录
RUN mkdir -p /root/.jupyter

# 生成Jupyter配置文件
RUN jupyter notebook --generate-config

# 配置Jupyter允许远程访问
RUN echo "c.ServerApp.ip = '0.0.0.0'" >> /root/.jupyter/jupyter_notebook_config.py && \
    echo "c.ServerApp.allow_root = True" >> /root/.jupyter/jupyter_notebook_config.py && \
    echo "c.ServerApp.open_browser = False" >> /root/.jupyter/jupyter_notebook_config.py && \
    echo "c.ServerApp.token = ''" >> /root/.jupyter/jupyter_notebook_config.py && \
    echo "c.ServerApp.password = ''" >> /root/.jupyter/jupyter_notebook_config.py && \
    echo "c.ServerApp.port = 8888" >> /root/.jupyter/jupyter_notebook_config.py

# 暴露端口
EXPOSE 5678
EXPOSE 8888

# 启动脚本
COPY start.sh /start.sh
RUN chmod +x /start.sh

# 使用启动脚本
CMD ["/start.sh"]

说明

  • 使用Ubuntu 24.04作为基础镜像
  • 安装libpq-dev以支持PostgreSQL客户端
  • 安装debugpy用于VS Code远程调试
  • 暴露5678端口供调试器连接
  • 暴露8888端口供宿主机直接访问Jupyter Notebook

3.2 创建启动脚本

编辑start.sh文件:

#!/bin/bash
# 容器启动脚本:同时运行Jupyter和保持容器活跃

echo "======================================"
echo "启动Python开发环境"
echo "======================================"

# 启动Jupyter Notebook(后台运行)
echo "正在启动Jupyter Notebook..."
jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser --allow-root \
    --notebook-dir=/workspace \
    > /var/log/jupyter.log 2>&1 &

# 等待Jupyter启动
sleep 3

# 显示Jupyter访问信息
echo ""
echo "======================================"
echo "✓ Jupyter Notebook已启动"
echo "======================================"
echo "访问地址: http://localhost:8888"
echo "工作目录: /workspace"
echo ""
echo "日志位置: /var/log/jupyter.log"
echo "======================================"
echo ""

# 保持容器运行
tail -f /dev/null

说明

  • 自动启动Jupyter Notebook
  • 在后台运行,不阻塞容器
  • 显示访问信息
  • 保持容器运行状态

3.3 创建requirements.txt

编辑requirements.txt,添加Python依赖:

# 数据库相关
psycopg2-binary==2.9.9
python-dotenv==1.0.0

# 调试工具
debugpy==1.8.0

# Jupyter相关
jupyter==1.0.0
jupyterlab==4.0.9
notebook==7.0.6
ipykernel==6.27.1
ipywidgets==8.1.1

# 数据分析常用库(可选但推荐)
pandas==2.1.4
numpy==1.26.2
matplotlib==3.8.2
seaborn==0.13.0
plotly==5.18.0

# SQL查询工具(在Jupyter中更方便地查询数据库)
ipython-sql==0.5.0
sqlalchemy==2.0.23

4. 配置PostgreSQL容器

4.1 创建docker-compose.yml

编辑docker-compose.yml(Podman也支持docker-compose格式):

version: '3.8'

services:
  # Python开发容器(含Jupyter)
  python-dev:
    build: .
    container_name: python-dev-jupyter
    hostname: python-dev
    volumes:
      - ./src:/workspace/src:z
      - ./notebooks:/workspace/notebooks:z
      - ./data:/workspace/data:z
    ports:
      - "5678:5678"  # debugpy调试端口
      - "8888:8888"  # Jupyter端口
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - python-dev-network
    stdin_open: true
    tty: true
    environment:
      - JUPYTER_ENABLE_LAB=yes
      - PYTHONUNBUFFERED=1
      - TZ=Asia/Shanghai
    restart: unless-stopped

  # PostgreSQL数据库容器
  postgres:
    image: postgres:16
    container_name: postgres-pydev
    hostname: postgres-pydev
    environment:
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: devpassword
      POSTGRES_DB: devdb
      TZ: Asia/Shanghai
      PGTZ: Asia/Shanghai
    volumes:
      - ./postgres-data:/var/lib/postgresql/data:z
    ports:
      - "5432:5432"
    networks:
      - python-dev-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

networks:
  python-dev-network:
    driver: bridge

配置说明

  • 两个服务python-dev(开发容器)和postgres(数据库)
  • volumes挂载
    • ./src:/workspace/src:本地代码实时同步到容器
    • ./postgres-data:数据库数据持久化到本地
    • :z标志:在SELinux环境下正确设置权限(Mac/Windows会自动忽略)
  • 网络配置:两个容器位于同一网络中,可通过容器名互相访问
  • 端口映射
    • 5678:调试端口
    • 5432:数据库端口(可选,允许宿主机直接访问数据库)
    • 8888:供浏览器访问Jupyter
  • depends_on:确保postgres容器先启动
  • healthcheck:检查数据库是否就绪

5. 验证环境

5.1 构建并启动容器

在项目根目录执行:

# 构建镜像(首次运行或Dockerfile修改后需要)
podman-compose build

# 启动所有容器
podman-compose up -d

# 查看容器状态
podman ps

# 查看日志(如果有问题)
podman-compose logs

命令说明

  • build:根据Dockerfile构建Python开发镜像
  • up -d:后台启动所有服务
  • ps:查看运行中的容器
  • logs:查看容器日志,用于排查启动问题

5.2 创建测试脚本

编辑src/test_db.py

#!/usr/bin/env python3
"""
数据库连接测试脚本
验证Python环境和PostgreSQL连接是否正常
"""

import sys
import psycopg2
from psycopg2 import sql
from datetime import datetime

def print_python_version():
    """打印Python版本信息"""
    print("=" * 50)
    print("Python环境信息")
    print("=" * 50)
    print(f"Python版本: {sys.version}")
    print(f"Python路径: {sys.executable}")
    print()

def test_database_connection():
    """测试数据库连接"""
    print("=" * 50)
    print("PostgreSQL连接测试")
    print("=" * 50)
    
    # 数据库连接参数
    # 注意:这里使用容器名'postgres'作为主机名
    conn_params = {
        'host': 'postgres',  # docker-compose中的服务名
        'port': 5432,
        'database': 'devdb',
        'user': 'devuser',
        'password': 'devpassword'
    }
    
    try:
        # 连接数据库
        print("正在连接数据库...")
        conn = psycopg2.connect(**conn_params)
        print("✓ 数据库连接成功!")
        
        # 创建游标
        cursor = conn.cursor()
        
        # 获取PostgreSQL版本
        cursor.execute("SELECT version();")
        db_version = cursor.fetchone()[0]
        print(f"\n数据库版本:\n{db_version}")
        
        # 创建测试表
        print("\n正在创建测试表...")
        cursor.execute("""
            DROP TABLE IF EXISTS test_table;
            CREATE TABLE test_table (
                id SERIAL PRIMARY KEY,
                message VARCHAR(255),
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
        """)
        conn.commit()
        print("✓ 测试表创建成功!")
        
        # 插入测试数据
        print("\n正在插入测试数据...")
        test_messages = [
            "Hello from Python 3.13!",
            "PostgreSQL 16 is working!",
            "Podman environment is ready!"
        ]
        
        for msg in test_messages:
            cursor.execute(
                "INSERT INTO test_table (message) VALUES (%s)",
                (msg,)
            )
        conn.commit()
        print(f"✓ 成功插入 {len(test_messages)} 条记录!")
        
        # 查询并显示数据
        print("\n查询结果:")
        print("-" * 50)
        cursor.execute("SELECT id, message, created_at FROM test_table ORDER BY id;")
        rows = cursor.fetchall()
        
        for row in rows:
            print(f"ID: {row[0]}")
            print(f"消息: {row[1]}")
            print(f"创建时间: {row[2]}")
            print("-" * 50)
        
        # 清理测试表
        cursor.execute("DROP TABLE test_table;")
        conn.commit()
        print("\n✓ 测试完成,清理测试表成功!")
        
        # 关闭连接
        cursor.close()
        conn.close()
        print("✓ 数据库连接已关闭")
        
        return True
        
    except psycopg2.Error as e:
        print(f"✗ 数据库错误: {e}")
        return False
    except Exception as e:
        print(f"✗ 发生错误: {e}")
        return False

def main():
    """主函数"""
    print("\n" + "=" * 50)
    print("开发环境验证脚本")
    print("=" * 50 + "\n")
    
    # 测试Python版本
    print_python_version()
    
    # 测试数据库连接
    success = test_database_connection()
    
    # 总结
    print("\n" + "=" * 50)
    if success:
        print("✓ 所有测试通过!环境配置成功!")
    else:
        print("✗ 测试失败,请检查配置")
    print("=" * 50 + "\n")
    
    return 0 if success else 1

if __name__ == "__main__":
    sys.exit(main())

脚本说明

  • 验证Python 3.12是否正确安装
  • 测试PostgreSQL连接(使用容器名postgres作为host)
  • 执行基本的CRUD操作,确保数据库正常工作
  • 提供清晰的输出,便于排查问题

5.3 运行测试脚本

# 进入Python开发容器
podman exec -it python-dev-jupyter bash

# 在容器内运行测试脚本
cd /workspace
python src/test_db.py

# 退出容器
exit

预期输出

==================================================
开发环境验证脚本
==================================================

==================================================
Python环境信息
==================================================
Python版本: 3.12.x ...
Python路径: /usr/bin/python

==================================================
PostgreSQL连接测试
==================================================
正在连接数据库...
✓ 数据库连接成功!

数据库版本:
PostgreSQL 16.x ...
...
==================================================
✓ 所有测试通过!环境配置成功!
==================================================

5.4 访问Jupyter

在浏览器中打开:

http://localhost:8888

你会看到JupyterLab界面。如果显示经典Notebook界面,可以在URL中添加/lab切换到Lab模式。

5.5 创建示例Notebook

用宿主机上的VS Code打开notebooks/01_database_test.ipynb,依次插入以下代码片段并保存:

# 导入必要的库
import psycopg2
import pandas as pd
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')

print("✓ 库导入成功")
print(f"Python版本: {psycopg2.extensions.libpq_version()}")
# 数据库连接参数
conn_params = {
    'host': 'postgres',
    'port': 5432,
    'database': 'devdb',
    'user': 'devuser',
    'password': 'devpassword'
}

try:
    conn = psycopg2.connect(**conn_params)
    print("✓ 数据库连接成功!")
    
    # 获取数据库版本
    cursor = conn.cursor()
    cursor.execute("SELECT version();")
    version = cursor.fetchone()[0]
    print(f"\n数据库版本:\n{version}")
    cursor.close()
    
except Exception as e:
    print(f"✗ 连接失败: {e}")
cursor = conn.cursor()

# 创建示例表
cursor.execute("""
    DROP TABLE IF EXISTS sales_data;
    CREATE TABLE sales_data (
        id SERIAL PRIMARY KEY,
        product_name VARCHAR(100),
        category VARCHAR(50),
        price DECIMAL(10, 2),
        quantity INTEGER,
        sale_date DATE,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
""")

# 插入示例数据
sample_data = [
    ('笔记本电脑', '电子产品', 5999.00, 3, '2024-01-15'),
    ('无线鼠标', '电子产品', 129.00, 10, '2024-01-16'),
    ('机械键盘', '电子产品', 599.00, 5, '2024-01-17'),
    ('显示器', '电子产品', 1999.00, 2, '2024-01-18'),
    ('USB-C数据线', '配件', 49.00, 20, '2024-01-19'),
    ('笔记本支架', '配件', 89.00, 8, '2024-01-20'),
]

for data in sample_data:
    cursor.execute("""
        INSERT INTO sales_data (product_name, category, price, quantity, sale_date)
        VALUES (%s, %s, %s, %s, %s)
    """, data)

conn.commit()
print(f"✓ 成功创建表并插入 {len(sample_data)} 条数据")
cursor.close()
# 使用pandas读取数据
query = "SELECT * FROM sales_data ORDER BY sale_date"
df = pd.read_sql_query(query, conn)

print("数据概览:")
print(df.head())

print("\n数据统计:")
print(df.describe())
import matplotlib.pyplot as plt
import seaborn as sns

# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

# 按类别统计销售额
df['total_sales'] = df['price'] * df['quantity']
category_sales = df.groupby('category')['total_sales'].sum().sort_values(ascending=False)

# 创建图表
fig, axes = plt.subplots(1, 2, figsize=(15, 5))

# 柱状图:按类别的销售额
category_sales.plot(kind='bar', ax=axes[0], color='skyblue')
axes[0].set_title('Sales by Category', fontsize=14, fontweight='bold')
axes[0].set_xlabel('Category')
axes[0].set_ylabel('Total Sales (CNY)')
axes[0].tick_params(axis='x', rotation=45)

# 饼图:销售数量分布
df.groupby('category')['quantity'].sum().plot(
    kind='pie', 
    ax=axes[1], 
    autopct='%1.1f%%',
    startangle=90
)
axes[1].set_title('Quantity Distribution by Category', fontsize=14, fontweight='bold')
axes[1].set_ylabel('')

plt.tight_layout()
plt.show()

print("\n各类别销售统计:")
print(category_sales)

在宿主机的浏览器中打开http://localhost:8888,在Jupyter界面中打开notebooks/01_database_test.ipynb,依次执行5个cell中的代码片段。

预期输出

✓ 库导入成功
Python版本: ...

✓ 数据库连接成功!
数据库版本:
PostgreSQL 16.x ...

✓ 成功创建表并插入 6 条数据
...

(柱状图和饼图)

6. 配置VS Code远程调试

6.1 安装VS Code扩展

在VS Code中安装以下扩展:

  1. Python (Microsoft)
  2. Dev Containers (Microsoft)
  3. Pylance (Microsoft)

6.2 配置调试器

编辑.vscode/launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Remote Attach (container)",
            "type": "python",
            "request": "attach",
            "connect": { "host": "localhost", "port": 5678 },
            "pathMappings": [
                { 
                    "localRoot": "${workspaceFolder}/src", 
                    "remoteRoot": "/workspace/src" 
                }
            ],
            "justMyCode": false
        }
    ]
}

配置说明

  • Remote Attach:连接到容器内运行的debugpy服务器
  • pathMappings:映射本地路径和容器路径,确保断点正确命中
  • port 5678:debugpy默认端口

6.3 创建调试脚本示例

编辑src/debug_example.py

#!/usr/bin/env python3
"""
调试示例脚本
演示如何使用VS Code进行远程调试
"""

import psycopg2

def connect_database():
    """连接数据库示例函数"""
    conn = psycopg2.connect(
        host='postgres',
        port=5432,
        database='devdb',
        user='devuser',
        password='devpassword'
    )
    return conn

def main():
    print("开始调试示例程序...")

    # 在这里设置断点进行调试
    conn = connect_database()
    cursor = conn.cursor()

    # 执行查询
    cursor.execute("SELECT current_database(), current_user;")
    result = cursor.fetchone()

    print(f"当前数据库: {result[0]}")
    print(f"当前用户: {result[1]}")

    cursor.close()
    conn.close()

    print("程序执行完成!")

if __name__ == "__main__":
    main()

6.4 使用调试器

  1. 在宿主机上打开终端,运行:

    # 在容器中运行你的脚本,并让 debugpy 监听 5678 端口
    podman exec -it python-dev-jupyter \
      python -m debugpy --listen 0.0.0.0:5678 --wait-for-client \
      /workspace/src/debug_example.py

    说明

    • 使用debugpy启动远程调试器,并等待VS Code连接
    • 在程序结束前,请勿关闭此终端窗口
  2. 在宿主机的VS Code中打开src/debug_example.py

  3. 在需要调试的代码行设置断点(点击行号左侧)

  4. F5或点击"运行和调试"

  5. 等待程序运行并命中断点

    image.png

注意:这种方式下,VS Code会连接到容器内执行的Python进程进行调试。


About

Container with Ubuntu 24.04 + Python 3.12 + Jupyter + debugpy

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors