Skip to content

Update docker.nix

Update docker.nix #39

Workflow file for this run

name: Build and Push Docker Images with Nix Flakes
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * 1' # Weekly on Monday at 2 AM
workflow_dispatch:
inputs:
push_images:
description: 'Push images to registry'
type: boolean
default: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
security-events: write
actions: read
checks: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix 2.31.1
run: |
# 安装最新稳定版 Nix 2.31.1 (单用户模式)
sh <(curl -L https://nixos.org/nix/install) --no-daemon --yes
# 重新加载环境
. /home/runner/.nix-profile/etc/profile.d/nix.sh
# 验证安装
nix --version
- name: Configure Nix
run: |
# 加载 Nix 环境
. /home/runner/.nix-profile/etc/profile.d/nix.sh
# 配置 Nix 以支持 Flakes
mkdir -p ~/.config/nix
cat > ~/.config/nix/nix.conf << EOF
experimental-features = nix-command flakes
allow-import-from-derivation = true
EOF
# 验证配置
nix --version
- name: Clean build environment
run: |
# 加载 Nix 环境
. /home/runner/.nix-profile/etc/profile.d/nix.sh
# 清理 Nix 缓存
echo "🧹 Cleaning Nix cache..."
nix-collect-garbage
nix store gc
# 清理 Docker 环境
echo "🧹 Cleaning Docker environment..."
docker image prune -f
docker container prune -f
docker builder prune -f
# 清理可能冲突的镜像
docker rmi ghcr.io/reaslab/docker-python-runner:secure-latest 2>/dev/null || echo " No existing image to remove"
- name: Setup Nix Flake environment
run: |
# 加载 Nix 环境
. /home/runner/.nix-profile/etc/profile.d/nix.sh
# 设置环境变量以允许非自由包(Gurobi)
export NIXPKGS_ALLOW_UNFREE=1
# 进入 Nix 开发环境并构建
echo "🔧 Setting up Nix Flake environment..."
nix develop --command bash -c "
echo '🐍 Building Python Docker image with Nix Flakes...'
echo 'Current directory:'
pwd
echo 'Available files:'
ls -la
# 设置更宽松的构建选项
export NIX_BUILD_CORES=0
export NIX_CONF_DIR=/tmp/nix-conf
mkdir -p \$NIX_CONF_DIR
# 使用正确的 flake 输出路径构建
echo 'Starting build with detailed output...'
nix build .#docker-image --option sandbox false --impure --show-trace --verbose || {
echo '❌ First build attempt failed, trying with different options...'
# 尝试不同的构建选项
nix build .#docker-image --option sandbox false --impure --option max-jobs 1 --option cores 1 || {
echo '❌ Second build attempt failed, trying without rebuild...'
nix build .#docker-image --option sandbox false --impure --option max-jobs 1 --option cores 1
}
}
echo 'Build command completed, checking results...'
# 检查构建结果
echo '🔍 Checking build results...'
ls -la
# 验证构建结果
if [ -L result ]; then
echo '✅ Result symlink created successfully'
ls -la result
echo 'Result points to:'
readlink result
echo 'File exists and is readable:'
test -r result && echo 'Yes' || echo 'No'
else
echo '❌ Result symlink not found, checking for other outputs...'
# 查找可能的输出文件
find . -name '*.tar.gz' -o -name 'docker-image*' 2>/dev/null || echo 'No tar.gz files found'
# 检查 Nix store 中的构建结果
echo 'Checking Nix store for build results...'
nix-store --query --outputs \$(nix-instantiate .#docker-image) 2>/dev/null || echo 'No outputs found in store'
exit 1
fi
"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
if: github.event.inputs.push_images != 'false'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Load Docker image
run: |
# 加载 Nix 环境
. /home/runner/.nix-profile/etc/profile.d/nix.sh
# 设置环境变量以允许非自由包(Gurobi)
export NIXPKGS_ALLOW_UNFREE=1
# 在 Nix 开发环境中加载 Docker 镜像
echo "🐳 Loading Docker image from Nix build result..."
nix develop --command bash -c "
if [ -L result ]; then
echo '✅ Result symlink found, loading Docker image...'
echo 'Result file info:'
ls -la result
file result
echo 'Loading Docker image...'
docker load < result
echo '✅ Docker image loaded successfully'
# 显示加载的镜像信息
echo 'Loaded images:'
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}'
else
echo '❌ Result symlink not found, rebuilding...'
nix build .#docker-image --option sandbox false --impure
if [ -L result ]; then
docker load < result
echo '✅ Docker image rebuilt and loaded successfully'
else
echo '❌ Build failed, no result symlink created'
exit 1
fi
fi
"
- name: Tag Docker image
if: github.event.inputs.push_images != 'false'
run: |
# 加载 Nix 环境
. /home/runner/.nix-profile/etc/profile.d/nix.sh
# 设置环境变量以允许非自由包(Gurobi)
export NIXPKGS_ALLOW_UNFREE=1
# 在 Nix 开发环境中处理 Docker 镜像标签
echo "🏷️ Tagging Docker image..."
nix develop --command bash -c "
# 获取最新加载的镜像ID(通过比较加载前后的镜像列表)
echo 'Current Docker images:'
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}'
# 查找我们的镜像(通过仓库名或标签模式)
IMAGE_ID=\$(docker images --format '{{.ID}}' | head -1)
if [ -z \"\$IMAGE_ID\" ]; then
echo '❌ No Docker images found'
exit 1
fi
# 优先查找我们的镜像,而不是第一个镜像
OUR_IMAGE_ID=\$(docker images --format '{{.ID}} {{.Repository}}' | grep -E '(reaslab|docker-python-runner)' | head -1 | awk '{print \$1}')
if [ -n \"\$OUR_IMAGE_ID\" ]; then
IMAGE_ID=\"\$OUR_IMAGE_ID\"
echo \"Found our image ID: \$IMAGE_ID\"
else
echo \"Using first available image ID: \$IMAGE_ID\"
fi
echo \"Using image ID: \$IMAGE_ID\"
# 创建标签数组
TAGS=()
# 总是创建 latest 标签
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest\")
# 根据触发类型创建版本特定标签
if [ \"${{ github.event_name }}\" = \"push\" ] && [ \"${{ github.ref }}\" = \"refs/heads/main\" ]; then
# Main branch push: 创建时间戳和 SHA 标签
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-\$(date +%Y%m%d-%H%M%S)\")
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-${{ github.sha }}\")
elif [ \"${{ github.event_name }}\" = \"workflow_dispatch\" ]; then
# 手动触发: 只创建时间戳标签
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-\$(date +%Y%m%d-%H%M%S)\")
elif [ \"${{ github.event_name }}\" = \"schedule\" ]; then
# 定时触发: 只创建时间戳标签
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-\$(date +%Y%m%d-%H%M%S)\")
fi
# 使用所有标签进行标记
for tag in \"\${TAGS[@]}\"; do
echo \"Tagging with: \$tag\"
docker tag \"\$IMAGE_ID\" \"\$tag\"
done
echo \"All tags created successfully\"
echo \"Final tagged images:\"
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}' | grep -E \"(${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}|ghcr.io/reaslab)\"
"
- name: Push Docker image
if: github.event.inputs.push_images != 'false'
run: |
# 加载 Nix 环境
. /home/runner/.nix-profile/etc/profile.d/nix.sh
# 设置环境变量以允许非自由包(Gurobi)
export NIXPKGS_ALLOW_UNFREE=1
# 在 Nix 开发环境中推送 Docker 镜像
echo "🚀 Pushing Docker image..."
nix develop --command bash -c "
# 确保我们推送的是正确的镜像
echo 'Current Docker images before push:'
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}'
# 创建标签数组(与标记步骤相同)
TAGS=()
# 总是创建 latest 标签
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest\")
# 根据触发类型创建版本特定标签
if [ \"${{ github.event_name }}\" = \"push\" ] && [ \"${{ github.ref }}\" = \"refs/heads/main\" ]; then
# Main branch push: 创建时间戳和 SHA 标签
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-\$(date +%Y%m%d-%H%M%S)\")
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-${{ github.sha }}\")
elif [ \"${{ github.event_name }}\" = \"workflow_dispatch\" ]; then
# 手动触发: 只创建时间戳标签
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-\$(date +%Y%m%d-%H%M%S)\")
elif [ \"${{ github.event_name }}\" = \"schedule\" ]; then
# 定时触发: 只创建时间戳标签
TAGS+=(\"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-\$(date +%Y%m%d-%H%M%S)\")
fi
# 推送所有标签
for tag in \"\${TAGS[@]}\"; do
echo \"Pushing: \$tag\"
docker push \"\$tag\"
done
echo \"All tags pushed successfully\"
"
- name: Run security scan
if: github.event.inputs.push_images != 'false'
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest
format: 'sarif'
output: 'trivy-results.sarif'
continue-on-error: true
- name: Install jq for SARIF parsing
if: github.event.inputs.push_images != 'false'
run: |
sudo apt-get update
sudo apt-get install -y jq
- name: Display local security scan results
if: github.event.inputs.push_images != 'false' && always()
run: |
echo "## 🔍 Local Security Scan Results" >> $GITHUB_STEP_SUMMARY
if [ -f "trivy-results.sarif" ]; then
echo "✅ Trivy security scan completed successfully" >> $GITHUB_STEP_SUMMARY
# Extract vulnerability count
VULNERABILITIES=$(jq '.runs[0].results | length' trivy-results.sarif 2>/dev/null || echo "0")
echo "- **Vulnerabilities found:** $VULNERABILITIES" >> $GITHUB_STEP_SUMMARY
# Show high/critical vulnerabilities
HIGH_CRITICAL=$(jq '.runs[0].results[] | select(.level == "error" or .level == "warning") | .level' trivy-results.sarif 2>/dev/null | wc -l || echo "0")
echo "- **High/Critical issues:** $HIGH_CRITICAL" >> $GITHUB_STEP_SUMMARY
# Show scan summary
echo "### 📊 Scan Summary" >> $GITHUB_STEP_SUMMARY
echo "- **Image scanned:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest\`" >> $GITHUB_STEP_SUMMARY
echo "- **Scan format:** SARIF" >> $GITHUB_STEP_SUMMARY
echo "- **Results file:** \`trivy-results.sarif\`" >> $GITHUB_STEP_SUMMARY
# Show some sample vulnerabilities if any
if [ "$VULNERABILITIES" -gt 0 ]; then
echo "### 🚨 Sample Vulnerabilities" >> $GITHUB_STEP_SUMMARY
jq -r '.runs[0].results[0:3][] | "- **\(.level)**: \(.message.text)"' trivy-results.sarif 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "Unable to parse vulnerability details" >> $GITHUB_STEP_SUMMARY
else
echo "### ✅ No vulnerabilities found" >> $GITHUB_STEP_SUMMARY
echo "The Docker image appears to be secure!" >> $GITHUB_STEP_SUMMARY
fi
else
echo "⚠️ Security scan results not available" >> $GITHUB_STEP_SUMMARY
echo "The Trivy scan may have failed or the results file was not generated." >> $GITHUB_STEP_SUMMARY
fi
- name: Upload Trivy scan results
if: github.event.inputs.push_images != 'false' && github.ref == 'refs/heads/main'
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
continue-on-error: true
- name: Display security scan results
if: github.event.inputs.push_images != 'false' && always()
run: |
echo "## 🔒 Security Scan Results" >> $GITHUB_STEP_SUMMARY
# Check if Advanced Security is available
if [ -f "trivy-results.sarif" ]; then
echo "✅ Security scan completed successfully" >> $GITHUB_STEP_SUMMARY
echo "📊 Scan results saved to trivy-results.sarif" >> $GITHUB_STEP_SUMMARY
# Display scan summary
echo "### 📋 Scan Summary" >> $GITHUB_STEP_SUMMARY
if command -v jq >/dev/null 2>&1; then
VULNERABILITIES=$(jq '.runs[0].results | length' trivy-results.sarif 2>/dev/null || echo "0")
echo "- **Vulnerabilities found:** $VULNERABILITIES" >> $GITHUB_STEP_SUMMARY
fi
echo "### 📄 Full Report" >> $GITHUB_STEP_SUMMARY
echo "Detailed scan results are available in the SARIF file." >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Security scan results not available" >> $GITHUB_STEP_SUMMARY
echo "This may be due to:" >> $GITHUB_STEP_SUMMARY
echo "- Advanced Security not enabled for this repository" >> $GITHUB_STEP_SUMMARY
echo "- Scan failed to complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note:** Code scanning requires GitHub Advanced Security (paid feature)." >> $GITHUB_STEP_SUMMARY
echo "The Trivy scan still runs locally and results are available in the workflow logs." >> $GITHUB_STEP_SUMMARY
fi
test:
runs-on: ubuntu-latest
needs: build
if: github.event.inputs.push_images != 'false'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Test Docker image functionality
run: |
echo "Testing Docker image functionality..."
# 拉取镜像
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest
# 基本验证
echo "Image size: $(docker images --format '{{.Size}}' ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest)"
echo "Image user: $(docker inspect --format='{{.Config.User}}' ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest)"
# 测试容器创建
CONTAINER_ID=$(docker create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest echo "test")
if [ $? -eq 0 ]; then
echo "✅ Container creation successful"
docker rm $CONTAINER_ID
else
echo "❌ Container creation failed"
exit 1
fi
# 测试Python和UV(使用临时文件系统挂载)
echo "Testing Python:"
docker run --rm --tmpfs /tmp:noexec,nosuid,size=100m --user root ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest /bin/bash -c "export PATH=\${PATH} && python --version" || echo "Python not found"
echo "Testing UV:"
docker run --rm --tmpfs /tmp:noexec,nosuid,size=100m --user root ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest /bin/bash -c "export PATH=\${PATH} && uv --version" || echo "UV not found"
# 测试安全限制
echo "Testing security restrictions:"
docker run --rm --tmpfs /tmp:noexec,nosuid,size=100m --user root ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest /bin/bash -c "python -c \"try: import os; print('ERROR: os should be restricted'); exit(1); except ImportError: print('OK: os is restricted')\"" || echo "Security test failed"
# 测试Gurobi可用性
echo "Testing Gurobi availability:"
docker run --rm --tmpfs /tmp:noexec,nosuid,size=100m --user root ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest /bin/bash -c "python -c \"import gurobipy; print('OK: Gurobi available')\"" || echo "Gurobi test failed"
# 测试科学计算包
echo "Testing scientific packages:"
docker run --rm --tmpfs /tmp:noexec,nosuid,size=100m --user root ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest /bin/bash -c "python -c \"import numpy, scipy, pandas; print('OK: Scientific packages available')\"" || echo "Scientific packages test failed"
echo "✅ All tests completed successfully"
generate-summary:
runs-on: ubuntu-latest
needs: [build, test]
if: always()
steps:
- name: Generate summary
run: |
echo "## 🐳 Docker Image Build Summary (Nix Flakes)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
echo "- \`secure-latest\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Build System:**" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Nix Flakes for reproducible builds" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Declarative environment management" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Features:**" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Secure Python 3.12 environment" >> $GITHUB_STEP_SUMMARY
echo "- ✅ UV package manager" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Gurobi optimization solver" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Scientific computing packages" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Non-root user execution" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Resource limits and security restrictions" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Security:**" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Trivy vulnerability scanning" >> $GITHUB_STEP_SUMMARY
echo "- ⚠️ Code scanning requires GitHub Advanced Security (paid feature)" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Local security scan results available in workflow logs" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Registry:** [GitHub Container Registry](https://github.com/orgs/reaslab/packages)" >> $GITHUB_STEP_SUMMARY