diff --git a/1-5-running-docker-compose/app-python/Dockerfile b/1-5-running-docker-compose/app-python/Dockerfile index a0f53b6..b1d2fd6 100644 --- a/1-5-running-docker-compose/app-python/Dockerfile +++ b/1-5-running-docker-compose/app-python/Dockerfile @@ -1,20 +1,30 @@ # Use an official Python runtime as a parent image -FROM python:2.7-slim +FROM python:3.11-slim # Set the working directory to /app WORKDIR /app +# Create a non-root user to run the application +RUN groupadd -r appuser && useradd -r -g appuser appuser + # Copy the app code and dependencies file into the container at /app -ADD . /app +# Use COPY instead of ADD for better transparency (security best practice) +COPY requirements.txt /app/ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . /app -# Install any needed packages specified in requirements.txt -RUN pip install -r requirements.txt +# Change ownership of the app directory to the non-root user +RUN chown -R appuser:appuser /app # Make port 80 available to the world outside this container EXPOSE 80 # Define environment variable -ENV NAME World +ENV NAME=World + +# Switch to non-root user +USER appuser # Run app.py when the container launches CMD ["python", "app.py"] \ No newline at end of file diff --git a/1-5-running-docker-compose/app-python/app.py b/1-5-running-docker-compose/app-python/app.py index ed77610..f3f0016 100644 --- a/1-5-running-docker-compose/app-python/app.py +++ b/1-5-running-docker-compose/app-python/app.py @@ -1,23 +1,85 @@ -from flask import Flask, render_template -from redis import Redis, RedisError +""" +Flask application with Redis counter. + +This application demonstrates a simple Flask web server that maintains +a visitor counter using Redis as a backend store. +""" +import logging import os import socket -# Connect to Redis -redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2) +from flask import Flask, render_template +from redis import Redis, RedisError + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +# Configuration from environment variables +REDIS_HOST = os.getenv("REDIS_HOST", "redis") +REDIS_DB = int(os.getenv("REDIS_DB", "0")) +REDIS_TIMEOUT = int(os.getenv("REDIS_TIMEOUT", "2")) +FLASK_HOST = os.getenv("FLASK_HOST", "0.0.0.0") +FLASK_PORT = int(os.getenv("FLASK_PORT", "80")) +FLASK_DEBUG = os.getenv("FLASK_DEBUG", "False").lower() == "true" + +# Connect to Redis with proper error handling +try: + redis = Redis( + host=REDIS_HOST, + db=REDIS_DB, + socket_connect_timeout=REDIS_TIMEOUT, + socket_timeout=REDIS_TIMEOUT + ) + # Test connection + redis.ping() + logger.info("Successfully connected to Redis at %s", REDIS_HOST) +except (RedisError, ConnectionError) as e: + logger.warning("Failed to connect to Redis: %s. Counter will be disabled.", str(e)) + redis = None app = Flask(__name__) + @app.route("/") def hello(): - try: - visits = redis.incr("counter") - except RedisError: + """ + Main route handler that displays a greeting page with visit counter. + + Returns: + str: Rendered HTML template with greeting message, hostname, and visit count + """ + visits = None + + if redis is not None: + try: + visits = redis.incr("counter") + logger.debug("Visit counter incremented to: %s", visits) + except RedisError as e: + logger.error("Redis error while incrementing counter: %s", str(e)) + visits = "cannot connect to Redis, counter disabled" + else: visits = "cannot connect to Redis, counter disabled" name = os.getenv("NAME", "world") hostname = socket.gethostname() - return render_template('hello.html', name=name, hostname=hostname, visits=visits) + + return render_template( + 'hello.html', + name=name, + hostname=hostname, + visits=visits + ) + if __name__ == "__main__": - app.run(host='0.0.0.0', port=80) \ No newline at end of file + logger.info( + "Starting Flask application on %s:%d (debug=%s)", + FLASK_HOST, + FLASK_PORT, + FLASK_DEBUG + ) + app.run(host=FLASK_HOST, port=FLASK_PORT, debug=FLASK_DEBUG) \ No newline at end of file diff --git a/1-5-running-docker-compose/app-python/requirements.txt b/1-5-running-docker-compose/app-python/requirements.txt index 8862084..e3e2d0a 100644 --- a/1-5-running-docker-compose/app-python/requirements.txt +++ b/1-5-running-docker-compose/app-python/requirements.txt @@ -1,2 +1,2 @@ -Flask -Redis \ No newline at end of file +Flask>=3.0.0 +redis>=5.0.0 \ No newline at end of file diff --git a/2-building-images/Dockerfile b/2-building-images/Dockerfile index a0f53b6..b1d2fd6 100644 --- a/2-building-images/Dockerfile +++ b/2-building-images/Dockerfile @@ -1,20 +1,30 @@ # Use an official Python runtime as a parent image -FROM python:2.7-slim +FROM python:3.11-slim # Set the working directory to /app WORKDIR /app +# Create a non-root user to run the application +RUN groupadd -r appuser && useradd -r -g appuser appuser + # Copy the app code and dependencies file into the container at /app -ADD . /app +# Use COPY instead of ADD for better transparency (security best practice) +COPY requirements.txt /app/ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . /app -# Install any needed packages specified in requirements.txt -RUN pip install -r requirements.txt +# Change ownership of the app directory to the non-root user +RUN chown -R appuser:appuser /app # Make port 80 available to the world outside this container EXPOSE 80 # Define environment variable -ENV NAME World +ENV NAME=World + +# Switch to non-root user +USER appuser # Run app.py when the container launches CMD ["python", "app.py"] \ No newline at end of file diff --git a/2-building-images/app.py b/2-building-images/app.py index ed77610..f3f0016 100644 --- a/2-building-images/app.py +++ b/2-building-images/app.py @@ -1,23 +1,85 @@ -from flask import Flask, render_template -from redis import Redis, RedisError +""" +Flask application with Redis counter. + +This application demonstrates a simple Flask web server that maintains +a visitor counter using Redis as a backend store. +""" +import logging import os import socket -# Connect to Redis -redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2) +from flask import Flask, render_template +from redis import Redis, RedisError + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +# Configuration from environment variables +REDIS_HOST = os.getenv("REDIS_HOST", "redis") +REDIS_DB = int(os.getenv("REDIS_DB", "0")) +REDIS_TIMEOUT = int(os.getenv("REDIS_TIMEOUT", "2")) +FLASK_HOST = os.getenv("FLASK_HOST", "0.0.0.0") +FLASK_PORT = int(os.getenv("FLASK_PORT", "80")) +FLASK_DEBUG = os.getenv("FLASK_DEBUG", "False").lower() == "true" + +# Connect to Redis with proper error handling +try: + redis = Redis( + host=REDIS_HOST, + db=REDIS_DB, + socket_connect_timeout=REDIS_TIMEOUT, + socket_timeout=REDIS_TIMEOUT + ) + # Test connection + redis.ping() + logger.info("Successfully connected to Redis at %s", REDIS_HOST) +except (RedisError, ConnectionError) as e: + logger.warning("Failed to connect to Redis: %s. Counter will be disabled.", str(e)) + redis = None app = Flask(__name__) + @app.route("/") def hello(): - try: - visits = redis.incr("counter") - except RedisError: + """ + Main route handler that displays a greeting page with visit counter. + + Returns: + str: Rendered HTML template with greeting message, hostname, and visit count + """ + visits = None + + if redis is not None: + try: + visits = redis.incr("counter") + logger.debug("Visit counter incremented to: %s", visits) + except RedisError as e: + logger.error("Redis error while incrementing counter: %s", str(e)) + visits = "cannot connect to Redis, counter disabled" + else: visits = "cannot connect to Redis, counter disabled" name = os.getenv("NAME", "world") hostname = socket.gethostname() - return render_template('hello.html', name=name, hostname=hostname, visits=visits) + + return render_template( + 'hello.html', + name=name, + hostname=hostname, + visits=visits + ) + if __name__ == "__main__": - app.run(host='0.0.0.0', port=80) \ No newline at end of file + logger.info( + "Starting Flask application on %s:%d (debug=%s)", + FLASK_HOST, + FLASK_PORT, + FLASK_DEBUG + ) + app.run(host=FLASK_HOST, port=FLASK_PORT, debug=FLASK_DEBUG) \ No newline at end of file diff --git a/2-building-images/requirements.txt b/2-building-images/requirements.txt index 8862084..e3e2d0a 100644 --- a/2-building-images/requirements.txt +++ b/2-building-images/requirements.txt @@ -1,2 +1,2 @@ -Flask -Redis \ No newline at end of file +Flask>=3.0.0 +redis>=5.0.0 \ No newline at end of file