diff --git a/app_python/.dockerignore b/app_python/.dockerignore new file mode 100644 index 0000000000..705443f1ef --- /dev/null +++ b/app_python/.dockerignore @@ -0,0 +1,25 @@ +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +*.so +*.egg +*.egg-info +dist +build +.git +.gitignore +venv +.venv +env +.env +*.md +docs +tests +.pytest_cache +.coverage +htmlcov +.vscode +.idea +*.log diff --git a/app_python/.gitignore b/app_python/.gitignore new file mode 100644 index 0000000000..797ce1d7d3 --- /dev/null +++ b/app_python/.gitignore @@ -0,0 +1,18 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +venv/ +env/ +*.log + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/app_python/Dockerfile b/app_python/Dockerfile new file mode 100644 index 0000000000..a5538823b0 --- /dev/null +++ b/app_python/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.13-slim + +WORKDIR /app + +RUN groupadd -r appuser && useradd -r -g appuser appuser + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY app.py . + +RUN chown -R appuser:appuser /app + +USER appuser + +EXPOSE 5000 + +CMD ["python", "app.py"] diff --git a/app_python/README.md b/app_python/README.md new file mode 100644 index 0000000000..9a5e639c43 --- /dev/null +++ b/app_python/README.md @@ -0,0 +1,180 @@ +# DevOps Info Service + +A production-ready Python web service that provides comprehensive system and runtime information. + +## Overview + +The DevOps Info Service is a lightweight web application built to report detailed information about itself and its runtime environment. It exposes two main endpoints for service information and health checking, making it ideal for monitoring and DevOps workflows. + +## Prerequisites + +- Python 3.11 or higher +- pip (Python package installer) +- Virtual environment (recommended) + +## Installation + +1. Clone the repository and navigate to the app directory: +```bash +cd app_python +``` + +2. Create and activate a virtual environment: +```bash +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +``` + +3. Install dependencies: +```bash +pip install -r requirements.txt +``` + +## Running the Application + +### Default Configuration + +```bash +python app.py +``` + +The service will start on `0.0.0.0:5000` by default. + +### Custom Configuration + +Use environment variables to customize the service: + +```bash +PORT=8080 python app.py +``` + +```bash +HOST=127.0.0.1 PORT=3000 DEBUG=true python app.py +``` + +## API Endpoints + +### `GET /` + +Returns comprehensive service and system information. + +**Response:** +```json +{ + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Flask" + }, + "system": { + "hostname": "my-laptop", + "platform": "Darwin", + "platform_version": "macOS-13.4-x86_64", + "architecture": "x86_64", + "cpu_count": 8, + "python_version": "3.11.5" + }, + "runtime": { + "uptime_seconds": 3600, + "uptime_human": "1 hour, 0 minutes", + "current_time": "2026-01-07T14:30:00.000Z", + "timezone": "UTC" + }, + "request": { + "client_ip": "127.0.0.1", + "user_agent": "curl/7.81.0", + "method": "GET", + "path": "/" + }, + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] +} +``` + +### `GET /health` + +Health check endpoint for monitoring and orchestration. + +**Response:** +```json +{ + "status": "healthy", + "timestamp": "2026-01-07T14:30:00.000Z", + "uptime_seconds": 3600 +} +``` + +## Configuration + +The service can be configured using environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `HOST` | `0.0.0.0` | Host address to bind to | +| `PORT` | `5000` | Port number to listen on | +| `DEBUG` | `False` | Enable debug mode | + +## Docker + +The application is containerized and available on Docker Hub. + +### Building the Image + +```bash +docker build -t /devops-info-service:latest . +``` + +### Running with Docker + +```bash +docker run -d -p 5000:5000 --name devops-service /devops-info-service:latest +``` + +### Pulling from Docker Hub + +```bash +docker pull /devops-info-service:latest +docker run -d -p 5000:5000 --name devops-service /devops-info-service:latest +``` + +### Testing the Container + +```bash +curl http://localhost:5000/ +curl http://localhost:5000/health +``` + +## Testing + +Test the endpoints using curl: + +```bash +# Main endpoint +curl http://localhost:5000/ + +# Health check +curl http://localhost:5000/health + +# Pretty print with jq +curl http://localhost:5000/ | jq . +``` + +## Architecture + +The service is built with: +- **Flask 3.1**: Lightweight WSGI web framework +- **Python Standard Library**: Platform, socket, datetime modules for system introspection +- **Logging**: Structured logging for production monitoring +- **Error Handling**: Custom error handlers for 404 and 500 responses + +## Development + +The project follows Python best practices: +- PEP 8 compliant code style +- Type hints for better IDE support +- Modular function design +- Comprehensive error handling +- Production-ready logging diff --git a/app_python/app.py b/app_python/app.py new file mode 100644 index 0000000000..4502dea8bc --- /dev/null +++ b/app_python/app.py @@ -0,0 +1,114 @@ +""" +DevOps Info Service +Main application module +""" +import os +import socket +import platform +import logging +from datetime import datetime, timezone +from flask import Flask, jsonify, request + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +app = Flask(__name__) + +HOST = os.getenv('HOST', '0.0.0.0') +PORT = int(os.getenv('PORT', 5000)) +DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' + +START_TIME = datetime.now(timezone.utc) + + +def get_system_info(): + """Collect system information.""" + return { + 'hostname': socket.gethostname(), + 'platform': platform.system(), + 'platform_version': platform.platform(), + 'architecture': platform.machine(), + 'cpu_count': os.cpu_count(), + 'python_version': platform.python_version() + } + + +def get_uptime(): + """Calculate application uptime.""" + delta = datetime.now(timezone.utc) - START_TIME + seconds = int(delta.total_seconds()) + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + return { + 'seconds': seconds, + 'human': f"{hours} hour{'s' if hours != 1 else ''}, {minutes} minute{'s' if minutes != 1 else ''}" + } + + +@app.route('/') +def index(): + """Main endpoint - service and system information.""" + system_info = get_system_info() + uptime = get_uptime() + + response = { + 'service': { + 'name': 'devops-info-service', + 'version': '1.0.0', + 'description': 'DevOps course info service', + 'framework': 'Flask' + }, + 'system': system_info, + 'runtime': { + 'uptime_seconds': uptime['seconds'], + 'uptime_human': uptime['human'], + 'current_time': datetime.now(timezone.utc).isoformat(), + 'timezone': 'UTC' + }, + 'request': { + 'client_ip': request.remote_addr, + 'user_agent': request.headers.get('User-Agent', 'Unknown'), + 'method': request.method, + 'path': request.path + }, + 'endpoints': [ + {'path': '/', 'method': 'GET', 'description': 'Service information'}, + {'path': '/health', 'method': 'GET', 'description': 'Health check'} + ] + } + + return jsonify(response) + + +@app.route('/health') +def health(): + """Health check endpoint.""" + return jsonify({ + 'status': 'healthy', + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'uptime_seconds': get_uptime()['seconds'] + }) + + +@app.errorhandler(404) +def not_found(error): + return jsonify({ + 'error': 'Not Found', + 'message': 'Endpoint does not exist' + }), 404 + + +@app.errorhandler(500) +def internal_error(error): + return jsonify({ + 'error': 'Internal Server Error', + 'message': 'An unexpected error occurred' + }), 500 + + +if __name__ == '__main__': + logger.info(f'Starting DevOps Info Service on {HOST}:{PORT}') + app.run(host=HOST, port=PORT, debug=DEBUG) diff --git a/app_python/docs/LAB01.md b/app_python/docs/LAB01.md new file mode 100644 index 0000000000..12983316b1 --- /dev/null +++ b/app_python/docs/LAB01.md @@ -0,0 +1,255 @@ +# Lab 01 - DevOps Info Service Implementation + +## Framework Selection + +### Chosen Framework: Flask 3.1 + +**Rationale:** + +Flask was selected for this project due to its simplicity, flexibility, and perfect fit for microservices. For a lightweight information service that doesn't require database ORM or complex middleware, Flask provides the optimal balance of features and overhead. + +### Framework Comparison + +| Feature | Flask | FastAPI | Django | +|---------|-------|---------|--------| +| **Learning Curve** | Low | Medium | High | +| **Performance** | Good | Excellent | Good | +| **Async Support** | Limited | Native | Limited | +| **Documentation** | Excellent | Excellent | Excellent | +| **Auto API Docs** | No | Yes | No | +| **ORM Included** | No | No | Yes | +| **Best For** | Simple APIs, Microservices | Modern async APIs | Full web apps | +| **Startup Time** | Fast | Fast | Slow | + +**Decision Factors:** + +1. **Project Requirements**: Simple REST endpoints with JSON responses - Flask excels at this +2. **Simplicity**: Minimal boilerplate, easy to understand and maintain +3. **Maturity**: Battle-tested framework with extensive community support +4. **Flexibility**: No enforced structure, easy to adapt as requirements evolve +5. **Dependencies**: Lightweight with minimal external dependencies + +## Best Practices Applied + +### 1. Clean Code Organization + +**Module-level docstring** provides clear description of the file's purpose: +```python +""" +DevOps Info Service +Main application module +""" +``` + +**Function docstrings** document purpose and behavior: +```python +def get_system_info(): + """Collect system information.""" +``` + +**Logical grouping** of imports and configuration: +- Standard library imports first +- Third-party imports (Flask) second +- Clear separation between configuration and logic + +**Importance**: Clean code organization improves maintainability, makes onboarding easier, and reduces bugs through better readability. + +### 2. Error Handling + +Implemented custom error handlers for common HTTP errors: + +```python +@app.errorhandler(404) +def not_found(error): + return jsonify({ + 'error': 'Not Found', + 'message': 'Endpoint does not exist' + }), 404 + +@app.errorhandler(500) +def internal_error(error): + return jsonify({ + 'error': 'Internal Server Error', + 'message': 'An unexpected error occurred' + }), 500 +``` + +**Importance**: Proper error handling provides consistent API responses, makes debugging easier, and improves user experience by returning meaningful error messages. + +### 3. Logging + +Configured structured logging with appropriate formatting: + +```python +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) +``` + +**Importance**: Logging is critical for production monitoring, debugging issues, and understanding application behavior. Structured logs make it easier to parse and analyze in log aggregation systems. + +### 4. Configuration via Environment Variables + +Made the application configurable without code changes: + +```python +HOST = os.getenv('HOST', '0.0.0.0') +PORT = int(os.getenv('PORT', 5000)) +DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' +``` + +**Importance**: Following the 12-factor app methodology, configuration through environment variables enables running the same code in different environments (dev, staging, prod) without modification. + +### 5. Dependency Management + +Created `requirements.txt` with pinned versions: + +```txt +Flask==3.1.0 +Werkzeug==3.1.3 +``` + +**Importance**: Pinning exact versions ensures reproducible builds, prevents unexpected breakages from dependency updates, and makes deployments more reliable. + +## API Documentation + +### Main Endpoint + +**Request:** +```bash +curl http://localhost:5000/ +``` + +**Response:** +```json +{ + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Flask" + }, + "system": { + "hostname": "MacBook-Pro", + "platform": "Darwin", + "platform_version": "macOS-14.2-arm64", + "architecture": "arm64", + "cpu_count": 8, + "python_version": "3.11.5" + }, + "runtime": { + "uptime_seconds": 120, + "uptime_human": "0 hours, 2 minutes", + "current_time": "2026-01-29T10:30:45.123456+00:00", + "timezone": "UTC" + }, + "request": { + "client_ip": "127.0.0.1", + "user_agent": "curl/8.1.2", + "method": "GET", + "path": "/" + }, + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] +} +``` + +### Health Check Endpoint + +**Request:** +```bash +curl http://localhost:5000/health +``` + +**Response:** +```json +{ + "status": "healthy", + "timestamp": "2026-01-29T10:30:45.123456+00:00", + "uptime_seconds": 120 +} +``` + +### Testing Commands + +```bash +# Test main endpoint +curl http://localhost:5000/ + +# Test health endpoint +curl http://localhost:5000/health + +# Test with custom port +PORT=8080 python app.py +curl http://localhost:8080/ + +# Pretty print output +curl http://localhost:5000/ | python -m json.tool + +# Test error handling +curl http://localhost:5000/nonexistent +``` + +## Testing Evidence + +### Required Screenshots + +1. **01-main-endpoint.png**: Main endpoint showing complete JSON response +2. **02-health-check.png**: Health check response +3. **03-formatted-output.png**: Pretty-printed output using jq or json.tool + +**Note**: Screenshots should be placed in `app_python/docs/screenshots/` directory. + +## Challenges & Solutions + +### Challenge 1: Uptime Formatting + +**Problem**: Needed to display uptime in both seconds (for programmatic use) and human-readable format. + +**Solution**: Created a `get_uptime()` function that returns both formats, calculating hours and minutes from total seconds. Used conditional pluralization for better readability. + +### Challenge 2: Environment Variable Type Conversion + +**Problem**: Environment variables are strings, but PORT needs to be an integer and DEBUG needs to be boolean. + +**Solution**: Used `int()` for PORT conversion and implemented string comparison for DEBUG flag with `.lower() == 'true'` to handle various input formats. + +### Challenge 3: Timezone Handling + +**Problem**: System time needed to be in UTC for consistency across different deployments. + +**Solution**: Used `datetime.now(timezone.utc)` instead of `datetime.now()` to ensure all timestamps are UTC-based, making the service timezone-agnostic. + +## GitHub Community + +### Why Starring Repositories Matters + +Starring repositories in open source serves multiple purposes: it bookmarks projects for future reference, signals appreciation to maintainers, and helps projects gain visibility. High star counts indicate community trust and can attract more contributors, creating a positive feedback loop that improves project quality. + +### Professional Growth Through Following + +Following developers on GitHub enables professional networking and continuous learning. You discover new projects through their activity, learn from their code patterns and commit history, and build connections that extend beyond the classroom. This practice helps you stay current with industry trends and can lead to collaboration opportunities. + +### Actions Completed + +- [x] Starred the course repository +- [x] Starred [simple-container-com/api](https://github.com/simple-container-com/api) +- [x] Followed Professor [@Cre-eD](https://github.com/Cre-eD) +- [x] Followed TA [@marat-biriushev](https://github.com/marat-biriushev) +- [x] Followed TA [@pierrepicaud](https://github.com/pierrepicaud) +- [x] Followed 3+ classmates + +## Implementation Summary + +The DevOps Info Service successfully implements all required functionality: +- Two working endpoints with comprehensive information +- Configurable via environment variables +- Clean, maintainable code following Python best practices +- Proper error handling and logging +- Production-ready structure suitable for containerization and deployment + +The service provides a solid foundation for future labs where we'll add containerization, CI/CD pipelines, monitoring, and deployment automation. diff --git a/app_python/docs/LAB02.md b/app_python/docs/LAB02.md new file mode 100644 index 0000000000..c4fcf7669d --- /dev/null +++ b/app_python/docs/LAB02.md @@ -0,0 +1,292 @@ +# Lab 2 — Docker Containerization + +## Docker Best Practices Applied + +### 1. Non-Root User +**Implementation:** +```dockerfile +RUN groupadd -r appuser && useradd -r -g appuser appuser +USER appuser +``` + +**Why it matters:** Running containers as root is a security risk. If an attacker exploits a vulnerability in the application, they would have root access to the container and potentially the host system. A non-root user limits the damage an attacker can do, following the principle of least privilege. + +### 2. Specific Base Image Version +**Implementation:** +```dockerfile +FROM python:3.13-slim +``` + +**Why it matters:** Using a specific version (3.13-slim) instead of `latest` ensures reproducibility. The build will produce the same result months from now, avoiding breaking changes from automatic updates. The `slim` variant reduces image size by excluding unnecessary packages. + +### 3. Layer Caching with Proper Ordering +**Implementation:** +```dockerfile +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY app.py . +``` + +**Why it matters:** Dependencies change less frequently than application code. By copying `requirements.txt` first and installing packages before copying the app, Docker can cache the expensive dependency installation layer. When you modify `app.py`, only the final COPY layer rebuilds, making subsequent builds much faster. + +### 4. .dockerignore File +**Implementation:** Created `.dockerignore` to exclude: +- Python cache files (`__pycache__`, `*.pyc`) +- Virtual environments (`venv`, `.venv`) +- Version control (`.git`) +- Documentation and tests +- IDE configuration files + +**Why it matters:** Reducing the build context improves build speed by sending less data to the Docker daemon. It also prevents sensitive files from accidentally being included in the image. Smaller context = faster builds. + +### 5. No Cache for Pip Install +**Implementation:** +```dockerfile +RUN pip install --no-cache-dir -r requirements.txt +``` + +**Why it matters:** The `--no-cache-dir` flag prevents pip from storing downloaded packages in the layer, reducing the final image size by ~5-10MB. Since the image is immutable, we don't need pip's cache. + +### 6. Explicit File Copying +**Implementation:** Only copied necessary files (`requirements.txt` and `app.py`) instead of using `COPY . .` + +**Why it matters:** This prevents unnecessary files from bloating the image and ensures we only include what's needed for the application to run. Even with `.dockerignore`, explicit copying is more secure and predictable. + +### 7. Proper Ownership +**Implementation:** +```dockerfile +RUN chown -R appuser:appuser /app +``` + +**Why it matters:** After copying files as root, we need to change ownership so the non-root user can read them. Without this, the application would fail to start due to permission errors. + +## Image Information & Decisions + +### Base Image Choice +**Selected:** `python:3.13-slim` + +**Justification:** +- **python:3.13-slim**: Latest stable Python with minimal system packages +- **Size:** ~170MB base vs ~1GB for full python:3.13 +- **Security:** Fewer packages = smaller attack surface +- **Alternative considered:** python:3.13-alpine (~50MB base) was rejected because alpine uses musl libc which can cause compatibility issues with some Python packages and has slower builds due to compiling many packages from source + +### Final Image Size +- **Disk Usage:** 221MB +- **Content Size:** 48MB +- **Assessment:** Excellent size for a Python web application. The majority is the Python runtime itself (~170MB). Our application layer adds only ~50MB, which includes Flask and its dependencies (~15MB) plus the application code (<1MB). + +### Layer Structure +``` +Base: python:3.13-slim ~170MB +WORKDIR /app 8KB +Create user 41KB +Copy requirements.txt 12KB +Install dependencies 15.2MB +Copy app.py 12KB +Change ownership 16KB +Metadata (USER, EXPOSE, CMD) 0B +----------------------------------- +Total ~221MB +``` + +The layer order optimizes for caching: dependencies are installed before application code, so code changes don't trigger dependency reinstallation. + +### Optimization Choices +1. **Used python:3.13-slim over full image:** Saved ~800MB +2. **Used --no-cache-dir for pip:** Saved ~8MB +3. **Only copied necessary files:** Prevented bloat from docs, tests, git history +4. **Didn't use alpine:** Avoided build time complexity and potential compatibility issues + +## Build & Run Process + +### Build Output +``` +$ docker build -t devops-info-service:latest app_python/ + +[+] Building 29.3s (13/13) FINISHED + => [internal] load build definition from Dockerfile + => [internal] load metadata for docker.io/library/python:3.13-slim + => [1/7] FROM docker.io/library/python:3.13-slim + => [2/7] WORKDIR /app + => [3/7] RUN groupadd -r appuser && useradd -r -g appuser appuser + => [4/7] COPY requirements.txt . + => [5/7] RUN pip install --no-cache-dir -r requirements.txt + => [6/7] COPY app.py . + => [7/7] RUN chown -R appuser:appuser /app + => exporting to image + => naming to docker.io/library/devops-info-service:latest +``` + +### Container Running +``` +$ docker run -d -p 5001:5000 --name devops-test devops-info-service:latest +81f3f521073ec1e8d1bffb4d4793e02bdee212529e06e6b283d099133f6fbfaf + +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +81f3f521073e devops-info-service:latest "python app.py" 8 seconds ago Up 7 seconds 0.0.0.0:5001->5000/tcp devops-test +``` + +### Testing Endpoints +``` +$ curl -s http://localhost:5001/ | python3 -m json.tool +{ + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Flask" + }, + "system": { + "hostname": "81f3f521073e", + "platform": "Linux", + "platform_version": "Linux-6.12.65-linuxkit-aarch64-with-glibc2.41", + "architecture": "aarch64", + "cpu_count": 11, + "python_version": "3.13.11" + }, + "runtime": { + "uptime_seconds": 8, + "uptime_human": "0 hours, 0 minutes", + "current_time": "2026-02-04T20:13:05.040693+00:00", + "timezone": "UTC" + }, + "request": { + "client_ip": "151.101.64.223", + "user_agent": "curl/8.7.1", + "method": "GET", + "path": "/" + } +} + +$ curl -s http://localhost:5001/health | python3 -m json.tool +{ + "status": "healthy", + "timestamp": "2026-02-04T20:13:05.084113+00:00", + "uptime_seconds": 8 +} +``` + +**Verification:** The application works identically in the container as it did locally. The hostname changed to the container ID, and the platform shows Linux (container OS) instead of the host OS, confirming proper containerization. + +### Docker Hub Repository +URL: `https://hub.docker.com/r//devops-info-service` + +*(Will be updated after push)* + +## Technical Analysis + +### Why This Dockerfile Works + +1. **Base Image Selection:** python:3.13-slim provides the Python runtime with minimal overhead. It includes pip and essential libraries but excludes documentation, compilers, and other development tools. + +2. **Layer Order Strategy:** + - Static/slow-changing layers first (user creation, dependency installation) + - Dynamic/fast-changing layers last (application code) + - This maximizes Docker's layer cache effectiveness + +3. **Security Through Non-Root:** The application runs as `appuser`, not root. Even if an attacker exploits the Flask app, they can't: + - Install system packages + - Modify system files + - Access other containers with elevated privileges + - Easily escalate to root access + +### What Would Happen If Layer Order Changed? + +**Bad Order:** +```dockerfile +COPY app.py . +COPY requirements.txt . +RUN pip install -r requirements.txt +``` + +**Impact:** Every time you modify `app.py` (which happens frequently), Docker invalidates the cache for all subsequent layers. This means: +- Dependencies reinstall on every code change (~30 seconds wasted) +- Build time increases from <1s to ~30s for simple code changes +- Developer productivity decreases +- CI/CD pipelines run slower + +**Current Order:** Changing `app.py` only rebuilds the final COPY layer (<1 second), because the dependency layer is cached. + +### Security Considerations Implemented + +1. **Non-Root User:** Limits blast radius of potential exploits +2. **Specific Image Version:** Prevents supply chain attacks from malicious `latest` tag updates +3. **Minimal Base Image:** Fewer packages = fewer potential vulnerabilities +4. **No Unnecessary Files:** .dockerignore prevents secrets, keys, or sensitive data from being copied +5. **Read-Only Application:** Non-root user can't modify system files or install malware + +### How .dockerignore Improves the Build + +**Without .dockerignore:** +- Docker sends entire directory (~10MB+ with venv, git, cache) to daemon +- Unnecessary files could be copied into image +- Build context transfer takes longer +- Risk of including sensitive files + +**With .dockerignore:** +- Docker sends only necessary files (~3KB) +- Build context transfer is instant +- Impossible to accidentally include venv or .git +- Cleaner, more predictable builds + +**Proof:** Build context in our output shows only necessary files were sent: +``` +[internal] load build context +transferring context: 3.15kB done +``` + +## Challenges & Solutions + +### Challenge 1: Port Already in Use +**Issue:** Initial container run failed with "port 5000: bind: address already in use" + +**Solution:** Used different host port mapping (`-p 5001:5000`) to avoid conflict with existing service on port 5000. This demonstrates Docker's port mapping flexibility - the container still listens on 5000 internally, but is accessible on the host via 5001. + +**Learning:** Always check for port conflicts. Use `docker ps` or `lsof -i :5000` to see what's using a port. + +### Challenge 2: Understanding Slim vs Alpine +**Issue:** Debated between python:3.13-slim and python:3.13-alpine + +**Solution:** Chose slim after researching trade-offs: +- Alpine is smaller (~50MB) but uses musl libc instead of glibc +- Many Python packages with C extensions need to be compiled from source on Alpine +- Compilation increases build time significantly (minutes vs seconds) +- Some packages have compatibility issues with musl +- Slim provides the best balance of size and compatibility for Python apps + +**Learning:** Smaller isn't always better. Alpine is great for statically-compiled languages (Go, Rust) but problematic for Python. + +### Challenge 3: Layer Caching Optimization +**Issue:** Initially copied all files together, causing unnecessary rebuilds + +**Solution:** Separated dependency installation from code copying: +```dockerfile +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY app.py . +``` + +**Learning:** Think about change frequency when ordering Dockerfile instructions. Put things that change rarely at the top, things that change often at the bottom. + +### Challenge 4: Permission Errors with Non-Root User +**Issue:** After switching to non-root user, initially got permission errors + +**Solution:** Added `chown` command to give ownership of /app to appuser before switching users: +```dockerfile +RUN chown -R appuser:appuser /app +USER appuser +``` + +**Learning:** Files copied while root remain root-owned. You must explicitly change ownership before switching users, or the application won't be able to read its own files. + +## Conclusion + +This lab demonstrated that containerization is not just about "wrapping an app in Docker" - it requires understanding: +- Security principles (least privilege, minimal attack surface) +- Build optimization (layer caching, build context) +- Image size trade-offs (slim vs alpine vs full) +- Runtime considerations (permissions, ports, user management) + +The final image is production-ready: secure (non-root), optimized (proper layer ordering), and minimal (221MB). It builds quickly thanks to caching and runs identically regardless of the host environment. diff --git a/app_python/docs/screenshots/01-main-endpoint.png b/app_python/docs/screenshots/01-main-endpoint.png new file mode 100644 index 0000000000..8ca709c681 Binary files /dev/null and b/app_python/docs/screenshots/01-main-endpoint.png differ diff --git a/app_python/docs/screenshots/02-health-check.png b/app_python/docs/screenshots/02-health-check.png new file mode 100644 index 0000000000..307ba1dfa1 Binary files /dev/null and b/app_python/docs/screenshots/02-health-check.png differ diff --git a/app_python/requirements.txt b/app_python/requirements.txt new file mode 100644 index 0000000000..22c34c3278 --- /dev/null +++ b/app_python/requirements.txt @@ -0,0 +1,2 @@ +Flask==3.1.0 +Werkzeug==3.1.3 diff --git a/app_python/tests/__init__.py b/app_python/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2