Set up Docker deployment for Flask applications with Gunicorn, automated versioning, and container registry publishing. Use when dockerizing a Flask app, containerizing for production, or setting up CI/CD with Docker.
npx claudepluginhub jmazzahacks/byteforge-claude-skills --plugin byteforge-skillsThis skill uses the workspace's default tool permissions.
This skill helps you containerize Flask applications using Docker with Gunicorn for production, automated version management, and seamless container registry publishing.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
This skill helps you containerize Flask applications using Docker with Gunicorn for production, automated version management, and seamless container registry publishing.
Use this skill when:
Before using this skill, ensure:
requirements.txt exists with all dependenciesIMPORTANT: Before creating files, ask the user these questions:
"What is your Flask application entry point?"
{module_name}:{app_variable}hyperopt_daemon:app or api_server:create_app()"What port does your Flask app use?"
"What is your container registry URL?"
ghcr.io/{org}/{project}docker.io/{user}/{project}{account}.dkr.ecr.{region}.amazonaws.com/{project}"Do you have private Git dependencies?" (yes/no)
"How many Gunicorn workers do you want?"
Create Dockerfile in the project root:
FROM python:3.13-slim
# Build argument for GitHub Personal Access Token (if needed for private deps)
ARG CR_PAT
ENV CR_PAT=${CR_PAT}
# Install curl (for health checks) and git (for private GitHub dependencies)
RUN apt-get update && apt-get install -y \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy requirements and install dependencies
COPY requirements.txt .
# Configure git to use PAT for GitHub access (if private deps)
RUN git config --global url."https://${CR_PAT}@github.com/".insteadOf "https://github.com/" \
&& pip install --no-cache-dir -r requirements.txt \
&& git config --global --unset url."https://${CR_PAT}@github.com/".insteadOf
# Copy application code
COPY . .
# Create non-root user for security
RUN useradd --create-home --shell /bin/bash appuser
RUN chown -R appuser:appuser /app
USER appuser
# Expose the application port
EXPOSE {port}
# Set environment variables
ENV PYTHONPATH=/app
ENV PORT={port}
# Run with gunicorn for production
# Port is read from PORT env var so it can be overridden at runtime
CMD gunicorn --bind 0.0.0.0:$PORT --workers {workers} {module}:{app}
CRITICAL Replacements:
{port} → Default application port (e.g., 5678). This is the default value for the PORT env var — it can be overridden at runtime with -e PORT=XXXX{workers} → Number of workers (e.g., 4, or 1 for background jobs){module} → Python module name (e.g., hyperopt_daemon){app} → App variable name (e.g., app or create_app())If NO private dependencies, remove these lines:
# Remove ARG CR_PAT, ENV CR_PAT, git installation, and git config commands
Simplified version without private deps:
FROM python:3.13-slim
# Install curl for health checks
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd --create-home --shell /bin/bash appuser
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE {port}
ENV PYTHONPATH=/app
ENV PORT={port}
CMD gunicorn --bind 0.0.0.0:$PORT --workers {workers} {module}:{app}
Create build-publish.sh in the project root:
#!/bin/sh
# VERSION file path
VERSION_FILE="VERSION"
# Parse command line arguments
NO_CACHE=""
if [ "$1" = "--no-cache" ]; then
NO_CACHE="--no-cache"
echo "Building with --no-cache flag"
fi
# Check if VERSION file exists, if not create it with version 1
if [ ! -f "$VERSION_FILE" ]; then
echo "1" > "$VERSION_FILE"
echo "Created VERSION file with initial version 1"
fi
# Read current version from file
CURRENT_VERSION=$(cat "$VERSION_FILE" 2>/dev/null)
# Validate that the version is a number
if ! echo "$CURRENT_VERSION" | grep -qE '^[0-9]+$'; then
echo "Error: Invalid version format in $VERSION_FILE. Expected a number, got: $CURRENT_VERSION"
exit 1
fi
# Increment version
VERSION=$((CURRENT_VERSION + 1))
echo "Building version $VERSION (incrementing from $CURRENT_VERSION)"
# Build the image with optional --no-cache flag
docker build $NO_CACHE --build-arg CR_PAT=$CR_PAT --platform linux/amd64 -t {registry_url}:$VERSION .
# Tag the same image as latest
docker tag {registry_url}:$VERSION {registry_url}:latest
# Push both tags
docker push {registry_url}:$VERSION
docker push {registry_url}:latest
# Update the VERSION file with the new version
echo "$VERSION" > "$VERSION_FILE"
echo "Updated $VERSION_FILE to version $VERSION"
CRITICAL Replacements:
{registry_url} → Full container registry URL (e.g., ghcr.io/mazza-vc/hyperopt-server)If NO private dependencies, remove --build-arg CR_PAT=$CR_PAT:
docker build $NO_CACHE --platform linux/amd64 -t {registry_url}:$VERSION .
Make the script executable:
chmod +x build-publish.sh
example.envCreate or update example.env with required environment variables for running the containerized application:
# Server Configuration
PORT={port}
# Database Configuration (if applicable)
{PROJECT_NAME}_DB_HOST=localhost
{PROJECT_NAME}_DB_NAME={project_name}
{PROJECT_NAME}_DB_USER={project_name}
{PROJECT_NAME}_DB_PASSWORD=your_password_here
# Build Configuration (for private dependencies)
CR_PAT=your_github_personal_access_token
# Optional: Additional app-specific variables
DEBUG=False
LOG_LEVEL=INFO
CRITICAL: Replace:
{port} → Application port (e.g., 5678){PROJECT_NAME} → Uppercase project name (e.g., "HYPEROPT_SERVER"){project_name} → Snake case project name (e.g., "hyperopt_server")Note: Remove CR_PAT if you don't have private dependencies.
Add VERSION file and .env to .gitignore:
# Environment variables
.env
# Version file (used by build system, not tracked)
VERSION
This prevents the VERSION file and environment secrets from being committed.
Create .dockerignore to exclude unnecessary files from Docker build context:
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
.venv/
ENV/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Environment files (secrets should not be in image)
.env
*.env
!example.env
# Testing
.pytest_cache/
.coverage
htmlcov/
.tox/
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# Git
.git/
.gitignore
# CI/CD
.github/
# Documentation
*.md
docs/
# Build artifacts
VERSION
*.log
# OS
.DS_Store
Thumbs.db
# Copy example environment file and configure
cp example.env .env
# Edit .env and fill in actual values
Load environment variables (if using .env):
# Export variables from .env for build process
set -a
source .env
set +a
Standard build (increments version, uses cache):
./build-publish.sh
Fresh build (no cache, pulls latest dependencies):
./build-publish.sh --no-cache
Using environment file:
docker run -p {port}:{port} \
--env-file .env \
{registry_url}:latest
Using explicit environment variables:
docker run -p {port}:{port} \
-e PORT={port} \
-e {PROJECT_NAME}_DB_PASSWORD=secret \
-e {PROJECT_NAME}_DB_HOST=db.example.com \
{registry_url}:latest
Test the container locally before publishing:
# Build without pushing
docker build --platform linux/amd64 -t {project}:test .
# Run locally
docker run -p {port}:{port} {project}:test
# Test the endpoint
curl http://localhost:{port}/health
This pattern follows these principles:
--platform linux/amd64 ensures compatibilityENV PORT=6100
CMD gunicorn --bind 0.0.0.0:$PORT --workers 4 app:create_app()
create_app()ENV PORT=5678
CMD gunicorn --bind 0.0.0.0:$PORT --workers 1 daemon:app
ENV PORT=7200
CMD gunicorn --bind 0.0.0.0:$PORT --workers 8 --timeout 120 api:app
Create the API first, then dockerize:
1. User: "Set up Flask API server"
2. [flask-smorest-api skill runs]
3. User: "Now dockerize it"
4. [flask-docker-deployment skill runs]
For database-dependent apps:
# Add psycopg2-binary to requirements.txt
# Set database env vars in docker run:
docker run -e DB_HOST=db.example.com -e DB_PASSWORD=secret ...
Login:
echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
Registry URL format:
ghcr.io/{org}/{project}
Login:
docker login docker.io
Registry URL format:
docker.io/{username}/{project}
Login:
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
{account}.dkr.ecr.us-east-1.amazonaws.com
Registry URL format:
{account}.dkr.ecr.{region}.amazonaws.com/{project}
chmod +x build-publish.sh
# Verify CR_PAT is set
echo $CR_PAT
# Test GitHub access
curl -H "Authorization: token $CR_PAT" https://api.github.com/user
# Check logs
docker logs {container_id}
# Run interactively to debug
docker run -it {registry_url}:latest /bin/bash
# If VERSION file gets corrupted, delete and rebuild
rm VERSION
./build-publish.sh
User: "Dockerize my Flask hyperopt server"
Claude asks:
hyperopt_daemon:app5678ghcr.io/mazza-vc/hyperopt-serveryes (arcana-core)1 (background job processor)Claude creates:
Dockerfile with gunicorn, 1 worker, port 5678build-publish.sh with GHCR registry URLVERSION to .gitignore.dockerignoreUser runs:
export CR_PAT=ghp_abc123
./build-publish.sh
Result:
ghcr.io/mazza-vc/hyperopt-server:1ghcr.io/mazza-vc/hyperopt-server:latest1Subsequent builds:
./build-publish.sh # Builds version 2
./build-publish.sh # Builds version 3
./build-publish.sh --no-cache # Builds version 4 (fresh)
latest tag for production/health endpoint for container orchestrationFor smaller images, use multi-stage builds:
# Build stage
FROM python:3.13-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.13-slim
# Install curl for health checks
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
RUN useradd --create-home appuser && chown -R appuser:appuser /app
USER appuser
ENV PORT=6100
CMD gunicorn --bind 0.0.0.0:$PORT --workers 4 app:app
This pattern: