dockerize app
This commit is contained in:
53
.dockerignore
Normal file
53
.dockerignore
Normal file
@@ -0,0 +1,53 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
*.so
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
.pytest_cache
|
||||
.coverage
|
||||
htmlcov
|
||||
.mypy_cache
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Virtual environments
|
||||
venv
|
||||
env
|
||||
ENV
|
||||
.venv
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
*.md
|
||||
|
||||
# Lock files (not needed for pip install)
|
||||
uv.lock
|
||||
|
||||
# Vercel
|
||||
vercel.json
|
||||
|
||||
# Distribution files
|
||||
*.tar.gz
|
||||
*.zip
|
||||
215
DOCKER.md
Normal file
215
DOCKER.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# Docker Setup Guide
|
||||
|
||||
This document explains how to build and run the Akern-Genai application using Docker.
|
||||
|
||||
## Overview
|
||||
|
||||
The Docker configuration includes:
|
||||
|
||||
- **Multi-stage build** for smaller image size
|
||||
- **Entrypoint script** with proper signal handling and exit status management
|
||||
- **Configurable port** via environment variables
|
||||
- **Health checks** for monitoring
|
||||
- **Non-root user** for security
|
||||
- **Optimized build** with `.dockerignore`
|
||||
|
||||
## Files
|
||||
|
||||
- [`Dockerfile`](Dockerfile) - Multi-stage Docker build configuration
|
||||
- [`entrypoint.sh`](entrypoint.sh) - Entrypoint script with signal handling
|
||||
- [`docker-compose.yml`](docker-compose.yml) - Docker Compose configuration
|
||||
- [`.dockerignore`](.dockerignore) - Files excluded from Docker build context
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables can be configured:
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ----------- | --------- | ----------------------------------------------------- |
|
||||
| `PORT` | `8000` | Port on which the application listens |
|
||||
| `HOST` | `0.0.0.0` | Host address to bind to |
|
||||
| `WORKERS` | `1` | Number of uvicorn worker processes |
|
||||
| `LOG_LEVEL` | `info` | Logging level (debug, info, warning, error, critical) |
|
||||
|
||||
## Building the Image
|
||||
|
||||
### Using Docker directly:
|
||||
|
||||
```bash
|
||||
docker build -t akern-genai:latest .
|
||||
```
|
||||
|
||||
### Using Docker Compose:
|
||||
|
||||
```bash
|
||||
docker-compose build
|
||||
```
|
||||
|
||||
## Running the Container
|
||||
|
||||
### Using Docker directly:
|
||||
|
||||
```bash
|
||||
# Run with default configuration (port 8000)
|
||||
docker run -p 8000:8000 akern-genai:latest
|
||||
|
||||
# Run with custom port
|
||||
docker run -p 3000:3000 -e PORT=3000 akern-genai:latest
|
||||
|
||||
# Run with multiple workers
|
||||
docker run -p 8000:8000 -e WORKERS=4 akern-genai:latest
|
||||
|
||||
# Run with custom log level
|
||||
docker run -p 8000:8000 -e LOG_LEVEL=debug akern-genai:latest
|
||||
```
|
||||
|
||||
### Using Docker Compose:
|
||||
|
||||
```bash
|
||||
# Run with default configuration
|
||||
docker-compose up
|
||||
|
||||
# Run with custom port
|
||||
PORT=3000 docker-compose up
|
||||
|
||||
# Run in detached mode
|
||||
docker-compose up -d
|
||||
|
||||
# Stop the container
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Using environment file:
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```env
|
||||
PORT=8000
|
||||
HOST=0.0.0.0
|
||||
WORKERS=1
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
## Signal Handling
|
||||
|
||||
The entrypoint script properly handles termination signals:
|
||||
|
||||
- **SIGTERM** - Graceful shutdown (sent by `docker stop`)
|
||||
- **SIGINT** - Interrupt signal (Ctrl+C)
|
||||
|
||||
The script will:
|
||||
|
||||
1. Forward the signal to uvicorn
|
||||
2. Wait up to 30 seconds for graceful shutdown
|
||||
3. Force kill if shutdown doesn't complete in time
|
||||
4. Exit with the same status code as uvicorn
|
||||
|
||||
## Health Check
|
||||
|
||||
The container includes a health check that monitors the application:
|
||||
|
||||
- **Interval**: 30 seconds
|
||||
- **Timeout**: 10 seconds
|
||||
- **Start period**: 5 seconds
|
||||
- **Retries**: 3
|
||||
|
||||
Check container health:
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
docker inspect <container_id> | grep -A 10 Health
|
||||
```
|
||||
|
||||
## Multi-Stage Build
|
||||
|
||||
The Dockerfile uses a two-stage build:
|
||||
|
||||
### Stage 1: Builder
|
||||
|
||||
- Installs build dependencies (gcc, musl-dev, etc.)
|
||||
- Creates a virtual environment
|
||||
- Installs Python dependencies
|
||||
|
||||
### Stage 2: Runtime
|
||||
|
||||
- Uses minimal Alpine Linux
|
||||
- Only includes runtime dependencies
|
||||
- Copies the virtual environment from builder
|
||||
- Runs as non-root user
|
||||
|
||||
This results in a significantly smaller final image.
|
||||
|
||||
## Security Features
|
||||
|
||||
- **Non-root user**: Application runs as `appuser` (UID 1000)
|
||||
- **Minimal base image**: Alpine Linux with only necessary packages
|
||||
- **No build tools in runtime**: Compiler and build tools are not included in final image
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container exits immediately
|
||||
|
||||
Check logs:
|
||||
|
||||
```bash
|
||||
docker logs <container_id>
|
||||
```
|
||||
|
||||
### Port already in use
|
||||
|
||||
Change the port:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:3000 -e PORT=3000 akern-genai:latest
|
||||
```
|
||||
|
||||
### Health check failing
|
||||
|
||||
Verify the application is running:
|
||||
|
||||
```bash
|
||||
docker exec -it <container_id> wget -O- http://localhost:8000/
|
||||
```
|
||||
|
||||
### View real-time logs
|
||||
|
||||
```bash
|
||||
docker logs -f <container_id>
|
||||
```
|
||||
|
||||
## Image Size Comparison
|
||||
|
||||
- **Before**: ~200-300 MB (single stage with build tools)
|
||||
- **After**: ~100-150 MB (multi-stage, minimal runtime)
|
||||
|
||||
## Production Considerations
|
||||
|
||||
For production deployment:
|
||||
|
||||
1. **Use specific image tags** instead of `latest`
|
||||
2. **Set appropriate worker count** based on CPU cores
|
||||
3. **Configure resource limits** in docker-compose.yml or Kubernetes
|
||||
4. **Use a reverse proxy** (nginx, traefik) for SSL termination
|
||||
5. **Set up logging aggregation** (ELK, Loki, etc.)
|
||||
6. **Configure monitoring** (Prometheus, Grafana)
|
||||
|
||||
Example docker-compose.yml with resource limits:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "2"
|
||||
memory: 1G
|
||||
reservations:
|
||||
cpus: "0.5"
|
||||
memory: 512M
|
||||
```
|
||||
82
Dockerfile
Normal file
82
Dockerfile
Normal file
@@ -0,0 +1,82 @@
|
||||
# ============================================
|
||||
# Stage 1: Builder
|
||||
# ============================================
|
||||
FROM python:3.13-alpine AS builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache \
|
||||
gcc \
|
||||
musl-dev \
|
||||
libffi-dev \
|
||||
openssl-dev \
|
||||
cargo
|
||||
|
||||
# Create virtual environment
|
||||
RUN python -m venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /build
|
||||
|
||||
# Copy requirements first for better caching
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# ============================================
|
||||
# Stage 2: Runtime
|
||||
# ============================================
|
||||
FROM python:3.13-alpine AS runtime
|
||||
|
||||
# Install runtime dependencies only
|
||||
RUN apk add --no-cache \
|
||||
libstdc++ \
|
||||
ca-certificates
|
||||
|
||||
# Create non-root user for security
|
||||
RUN addgroup -g 1000 appuser && \
|
||||
adduser -D -u 1000 -G appuser appuser
|
||||
|
||||
# Copy virtual environment from builder
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
# Create application directory
|
||||
RUN mkdir /app && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy application files
|
||||
COPY --chown=appuser:appuser app.py .
|
||||
COPY --chown=appuser:appuser main.py .
|
||||
COPY --chown=appuser:appuser static ./static
|
||||
|
||||
# Copy and setup entrypoint script
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Switch to non-root user
|
||||
USER appuser
|
||||
|
||||
# Expose default port (can be overridden via PORT env var)
|
||||
EXPOSE 8000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT:-8000}/ || exit 1
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PORT=8000 \
|
||||
HOST=0.0.0.0 \
|
||||
WORKERS=1 \
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Use entrypoint script
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
|
||||
32
docker-compose.yml
Normal file
32
docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: akern-genai-app
|
||||
ports:
|
||||
- "${PORT:-8000}:8000"
|
||||
environment:
|
||||
# Application configuration
|
||||
- PORT=${PORT:-8000}
|
||||
- HOST=${HOST:-0.0.0.0}
|
||||
- WORKERS=${WORKERS:-1}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
|
||||
# Google GenAI configuration (load from .env file)
|
||||
- GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://${HOST:-127.0.0.1}:${PORT:-8000}/",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
55
entrypoint.sh
Normal file
55
entrypoint.sh
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Default values
|
||||
PORT="${PORT:-8000}"
|
||||
HOST="${HOST:-0.0.0.0}"
|
||||
WORKERS="${WORKERS:-1}"
|
||||
LOG_LEVEL="${LOG_LEVEL:-info}"
|
||||
|
||||
# Display configuration
|
||||
echo "=========================================="
|
||||
echo "Starting FastAPI application"
|
||||
echo "=========================================="
|
||||
echo "Host: ${HOST}"
|
||||
echo "Port: ${PORT}"
|
||||
echo "Workers: ${WORKERS}"
|
||||
echo "Log Level: ${LOG_LEVEL}"
|
||||
echo "=========================================="
|
||||
|
||||
# Trap signals for graceful shutdown
|
||||
cleanup() {
|
||||
echo "Received termination signal, shutting down gracefully..."
|
||||
# Send SIGTERM to uvicorn process group
|
||||
if [ -n "$UVICORN_PID" ]; then
|
||||
kill -TERM "$UVICORN_PID" 2>/dev/null || true
|
||||
# Wait for uvicorn to exit, but with a timeout
|
||||
timeout 30 sh -c "while kill -0 $UVICORN_PID 2>/dev/null; do sleep 1; done" || {
|
||||
echo "Uvicorn did not shut down gracefully, forcing exit..."
|
||||
kill -KILL "$UVICORN_PID" 2>/dev/null || true
|
||||
}
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Register signal handlers
|
||||
trap cleanup SIGTERM SIGINT
|
||||
|
||||
# Start uvicorn in the background to capture PID
|
||||
uvicorn app:app \
|
||||
--host "${HOST}" \
|
||||
--port "${PORT}" \
|
||||
--workers "${WORKERS}" \
|
||||
--log-level "${LOG_LEVEL}" \
|
||||
--access-log \
|
||||
&
|
||||
|
||||
UVICORN_PID=$!
|
||||
|
||||
# Wait for uvicorn process
|
||||
wait $UVICORN_PID
|
||||
EXIT_STATUS=$?
|
||||
|
||||
# Exit with the same status as uvicorn
|
||||
echo "Uvicorn exited with status: ${EXIT_STATUS}"
|
||||
exit ${EXIT_STATUS}
|
||||
Reference in New Issue
Block a user