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.
/plugin marketplace add jmazzahacks/byteforge-claude-skills/plugin install byteforge-skills@byteforge-claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
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
CMD ["gunicorn", "--bind", "0.0.0.0:{port}", "--workers", "{workers}", "{module}:{app}"]
CRITICAL Replacements:
{port} → Application port (e.g., 5678){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 compatibilityCMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:create_app()"]
create_app()CMD ["gunicorn", "--bind", "0.0.0.0:5678", "--workers", "1", "daemon:app"]
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--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
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:app"]
This pattern:
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.