Skip to content

Commit 30ace49

Browse files
committed
update docker python runner
1 parent 6d73156 commit 30ace49

File tree

7 files changed

+221
-230
lines changed

7 files changed

+221
-230
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ jobs:
8383
# 设置环境变量以允许非自由包(Gurobi)
8484
export NIXPKGS_ALLOW_UNFREE=1
8585
86+
# 生成当前UTC时间戳
87+
CURRENT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
88+
echo "Setting Docker image timestamp to: $CURRENT_TIMESTAMP"
89+
export DOCKER_IMAGE_TIMESTAMP="$CURRENT_TIMESTAMP"
90+
8691
# 进入 Nix 开发环境并构建
8792
echo "🔧 Setting up Nix Flake environment..."
8893
nix develop --command bash -c "

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ docker run --rm \
9191

9292
- **Core**: pip, setuptools, wheel
9393
- **Scientific**: numpy, scipy, pandas, matplotlib, scikit-learn
94+
- **Visualization**: seaborn
95+
- **Financial**: yfinance
9496
- **Optimization**: gurobipy (Gurobi 12.0.3)
9597
- **Build Tools**: cython
9698
- **Package Manager**: uv

build.sh

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,76 +8,89 @@ set -e
88
echo "=== Building Python Docker image with Nix dockerTools ==="
99
echo "🔨 Building secure Docker image with restricted Python environment..."
1010

11-
# 清理旧的镜像标签和可能冲突的镜像
11+
# Clean up old image tags and potentially conflicting images
1212
echo "🧹 Cleaning up old image tags and conflicting images..."
13-
# 删除目标标签
13+
14+
# First stop and remove containers using docker-python-runner images
15+
echo "🧹 Cleaning up containers using docker-python-runner images..."
16+
CONTAINERS_TO_STOP=$(docker ps -a --format "{{.ID}} {{.Image}}" | grep "docker-python-runner" | awk '{print $1}' || echo "")
17+
if [ -n "$CONTAINERS_TO_STOP" ]; then
18+
echo " Found containers using docker-python-runner images, stopping and removing them..."
19+
echo "$CONTAINERS_TO_STOP" | while read container_id; do
20+
if [ -n "$container_id" ]; then
21+
echo " Stopping container: $container_id"
22+
docker stop "$container_id" 2>/dev/null || echo " Container $container_id already stopped"
23+
echo " Removing container: $container_id"
24+
docker rm "$container_id" 2>/dev/null || echo " Container $container_id already removed"
25+
fi
26+
done
27+
else
28+
echo " No containers using docker-python-runner images found"
29+
fi
30+
31+
# Remove target tag
1432
docker rmi ghcr.io/reaslab/docker-python-runner:secure-latest 2>/dev/null || echo " No existing tag to remove"
1533

16-
# 清理所有悬空镜像
34+
# Clean up all dangling images
1735
echo "🧹 Cleaning up dangling images..."
1836
docker image prune -f 2>/dev/null || echo " No dangling images to remove"
1937

20-
# 清理可能冲突的镜像(通过镜像名称识别)
21-
echo "🧹 Cleaning up potentially conflicting images..."
22-
# 首先检查是否有ID冲突的镜像
23-
CONFLICTING_IMAGES=$(docker images --format "{{.ID}} {{.Repository}}:{{.Tag}}" | awk '{print $1}' | sort | uniq -d)
24-
if [ -n "$CONFLICTING_IMAGES" ]; then
25-
echo " Found conflicting image IDs: $CONFLICTING_IMAGES"
26-
for conflict_id in $CONFLICTING_IMAGES; do
27-
echo " Removing all tags for conflicting ID: $conflict_id"
28-
docker rmi "$conflict_id" 2>/dev/null || echo " Could not remove ID $conflict_id"
29-
done
30-
fi
31-
32-
# 清理Python/UV相关的镜像
33-
docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" | grep -E "(python|uv)" | while read repo_tag id; do
34-
if [ "$repo_tag" != "ghcr.io/reaslab/docker-python-runner:secure-latest" ]; then
35-
echo " Removing Python/UV related image: $repo_tag ($id)"
36-
docker rmi "$id" 2>/dev/null || echo " Could not remove $repo_tag"
37-
fi
38+
# Clean up docker-python-runner related images
39+
echo "🧹 Cleaning up docker-python-runner related images..."
40+
docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" | grep "docker-python-runner" | while read repo_tag id; do
41+
echo " Removing docker-python-runner image: $repo_tag ($id)"
42+
docker rmi -f "$id" 2>/dev/null || echo " Could not remove $repo_tag"
3843
done
3944

4045
echo "Building with Nix dockerTools..."
41-
# 配置 Nix 以支持 Flakes,与工作流保持一致
46+
# Configure Nix to support Flakes, consistent with workflow
4247
mkdir -p ~/.config/nix
4348
cat > ~/.config/nix/nix.conf << EOF
4449
experimental-features = nix-command flakes
4550
allow-import-from-derivation = true
4651
EOF
4752

48-
# 设置环境变量以允许非自由包(Gurobi
53+
# Set environment variable to allow unfree packages (Gurobi)
4954
export NIXPKGS_ALLOW_UNFREE=1
50-
# 使用 nix build 命令,与工作流保持一致
55+
56+
# Generate current UTC timestamp
57+
CURRENT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
58+
echo "Setting Docker image timestamp to: $CURRENT_TIMESTAMP"
59+
60+
# Use environment variable to pass timestamp to Nix
61+
export DOCKER_IMAGE_TIMESTAMP="$CURRENT_TIMESTAMP"
62+
63+
# Use nix build command, consistent with workflow
5164
nix build .#docker-image --option sandbox false --impure
5265

5366
echo "Loading Nix image into Docker..."
54-
# 记录加载前的镜像ID和标签
67+
# Record image IDs and tags before loading
5568
BEFORE_IMAGES=$(docker images --format "{{.ID}} {{.Repository}}:{{.Tag}}" | sort)
5669

57-
# 加载Nix构建的镜像
70+
# Load Nix-built image
5871
docker load < result
5972

60-
# 记录加载后的镜像ID和标签
73+
# Record image IDs and tags after loading
6174
AFTER_IMAGES=$(docker images --format "{{.ID}} {{.Repository}}:{{.Tag}}" | sort)
6275

6376
echo "Tagging image..."
64-
# 找出新加载的镜像(通过比较ID和标签)
77+
# Find newly loaded image (by comparing IDs and tags)
6578
NEW_IMAGE_INFO=$(comm -13 <(echo "$BEFORE_IMAGES") <(echo "$AFTER_IMAGES") | head -1)
6679

6780
if [ -n "$NEW_IMAGE_INFO" ]; then
6881
NEW_IMAGE_ID=$(echo "$NEW_IMAGE_INFO" | awk '{print $1}')
6982
NEW_IMAGE_TAG=$(echo "$NEW_IMAGE_INFO" | awk '{print $2}')
7083
echo " Found new image: $NEW_IMAGE_TAG ($NEW_IMAGE_ID)"
7184

72-
# 检查是否是我们期望的镜像
85+
# Check if this is our expected image
7386
if [[ "$NEW_IMAGE_TAG" == *"python"* ]] || [[ "$NEW_IMAGE_TAG" == *"uv"* ]] || [[ "$NEW_IMAGE_TAG" == *"reaslab"* ]]; then
7487
echo " Using new image ID: $NEW_IMAGE_ID"
7588
else
7689
echo " New image doesn't match expected pattern, using it anyway"
7790
fi
7891
else
7992
echo " No new image detected, checking for existing suitable images"
80-
# 查找现有的Python相关镜像
93+
# Find existing Python-related images
8194
EXISTING_PYTHON=$(docker images --format "{{.ID}} {{.Repository}}:{{.Tag}}" | grep -E "(python|uv|reaslab)" | head -1)
8295
if [ -n "$EXISTING_PYTHON" ]; then
8396
EXISTING_ID=$(echo "$EXISTING_PYTHON" | awk '{print $1}')
@@ -90,7 +103,7 @@ else
90103
fi
91104
fi
92105

93-
# 生成标签,与工作流保持一致
106+
# Generate tags, consistent with workflow
94107
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
95108
SHORT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "local")
96109

@@ -99,18 +112,20 @@ echo " - ghcr.io/reaslab/docker-python-runner:secure-latest"
99112
echo " - ghcr.io/reaslab/docker-python-runner:secure-$TIMESTAMP"
100113
echo " - ghcr.io/reaslab/docker-python-runner:secure-$SHORT_SHA"
101114

102-
# 创建多个标签,与工作流保持一致
115+
# Create multiple tags, consistent with workflow
103116
docker tag $NEW_IMAGE_ID ghcr.io/reaslab/docker-python-runner:secure-latest
104117
docker tag $NEW_IMAGE_ID ghcr.io/reaslab/docker-python-runner:secure-$TIMESTAMP
105118
docker tag $NEW_IMAGE_ID ghcr.io/reaslab/docker-python-runner:secure-$SHORT_SHA
106119

107-
# 验证最终镜像状态
120+
# Verify final image state
108121
echo "🔍 Verifying final image state..."
109122
FINAL_IMAGE_ID=$(docker images --format "{{.ID}}" ghcr.io/reaslab/docker-python-runner:secure-latest 2>/dev/null || echo "")
110123
if [ -n "$FINAL_IMAGE_ID" ]; then
111124
echo " Final image ID: $FINAL_IMAGE_ID"
125+
echo " Created: $(docker images --format "{{.CreatedAt}}" ghcr.io/reaslab/docker-python-runner:secure-latest)"
126+
echo " Size: $(docker images --format "{{.Size}}" ghcr.io/reaslab/docker-python-runner:secure-latest)"
112127

113-
# 检查是否有其他镜像使用相同的ID
128+
# Check if other images use the same ID
114129
DUPLICATE_TAGS=$(docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" | awk -v target_id="$FINAL_IMAGE_ID" '$2 == target_id && $1 != "ghcr.io/reaslab/docker-python-runner:secure-latest" {print $1}')
115130

116131
if [ -n "$DUPLICATE_TAGS" ]; then
@@ -130,13 +145,14 @@ fi
130145
echo "✅ Build completed successfully!"
131146

132147
echo "📋 Secure Image Configuration:"
133-
echo " - Python version: 3.12 (restricted environment)"
148+
echo " - Python Version: 3.12 (restricted environment)"
134149
echo " - Security: Dangerous modules blocked (os, subprocess, sys, etc.)"
135-
echo " - Resource limits: 1GB memory, CPU shares limit"
136-
echo " - Safe packages: pip, setuptools, wheel, cython, numpy, scipy, pandas, matplotlib, scikit-learn"
150+
echo " - Resource Limits: 1GB memory, CPU share limits"
151+
echo " - Safe Packages: pip, setuptools, wheel, cython, numpy, scipy, pandas, matplotlib, scikit-learn, yfinance, seaborn"
137152
echo " - Gurobi: 12.0.3 (via nixpkgs)"
138-
echo " - Container: Read-only rootfs, non-root user (1000:1000)"
153+
echo " - Container: Read-only root filesystem, non-root user (1000:1000)"
139154
echo " - Network: Restricted (disabled by default)"
140155
echo " - Tools: Minimal set (bash, coreutils, curl, tar, gzip)"
141-
echo " - Compilation tools: Removed for security"
156+
echo " - Compilation Tools: Removed for security"
157+
echo " - Module Installation: Support via UV installation to /tmp/.local/lib/python3.12/site-packages"
142158
echo "Image: ghcr.io/reaslab/docker-python-runner:secure-latest"

docker-compose.yml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ services:
1414
- ./gurobi.lic:/app/gurobi.lic:ro
1515
environment:
1616
- GRB_LICENSE_FILE=/app/gurobi.lic
17-
- PYTHONPATH=/app
17+
- PYTHONPATH=/app:/tmp/.local/lib/python3.12/site-packages
1818
working_dir: /app
1919
command: tail -f /dev/null
2020
# Security settings
@@ -27,10 +27,11 @@ services:
2727
- SETGID
2828
- SETUID
2929
tmpfs:
30-
- /tmp:noexec,nosuid,size=100m
31-
- /.cache:noexec,nosuid,size=1g,uid=1000,gid=1000,mode=755
32-
- /.local:nosuid,size=500m,uid=1000,gid=1000,mode=755
33-
- /.local/share:nosuid,size=1g,uid=1000,gid=1000,mode=755
30+
- /tmp:nosuid,size=2g,uid=1000,gid=1000,mode=755
31+
- /.cache:nosuid,size=1g,uid=1000,gid=1000,mode=755
32+
- /.local:nosuid,size=1g,uid=1000,gid=1000,mode=755
33+
- /.local/share:nosuid,size=512m,uid=1000,gid=1000,mode=755
34+
- /.uv_cache:nosuid,size=1g,uid=1000,gid=1000,mode=755
3435
networks:
3536
- python-network
3637

@@ -42,11 +43,17 @@ services:
4243
- ./gurobi.lic:/app/gurobi.lic:ro
4344
environment:
4445
- GRB_LICENSE_FILE=/app/gurobi.lic
45-
- PYTHONPATH=/app
46+
- PYTHONPATH=/app:/tmp/.local/lib/python3.12/site-packages
4647
working_dir: /app
4748
command: bash
4849
# Development mode - less restrictive
4950
user: "1000:1000"
51+
tmpfs:
52+
- /tmp:nosuid,size=2g,uid=1000,gid=1000,mode=755
53+
- /.cache:nosuid,size=1g,uid=1000,gid=1000,mode=755
54+
- /.local:nosuid,size=1g,uid=1000,gid=1000,mode=755
55+
- /.local/share:nosuid,size=512m,uid=1000,gid=1000,mode=755
56+
- /.uv_cache:nosuid,size=1g,uid=1000,gid=1000,mode=755
5057
networks:
5158
- python-network
5259

0 commit comments

Comments
 (0)