Deploying a web application securely to the cloud is a critical skill for any developer looking to build production-ready systems. This chapter guides you through taking your locally containerized, secure Flask application and deploying it to a public-facing cloud environment, focusing on hardening the infrastructure and configuration.
By the end of this milestone, you will have your secure Flask application running on Azure App Service, configured to use a managed PostgreSQL database, with all sensitive configurations handled through environment variables. This practical experience is vital for understanding how to maintain the security posture of an application once it leaves the development environment.
Project Overview: Hardening Cloud Deployments
This chapter focuses on the secure deployment of our Flask application. We will move our application from a local Docker Compose setup to a managed cloud environment, specifically Azure. The core objective is to ensure that the security measures implemented in previous chapters (secure authentication, input validation) are extended to the deployment phase, mitigating risks associated with public exposure and cloud infrastructure.
Specific Security Aspects Addressed:
- Secure Configuration: Using environment variables for sensitive data instead of hardcoding.
- Network Isolation: Restricting database access to only authorized application instances.
- Least Privilege: Running containers as non-root users and granting minimal necessary permissions to cloud resources.
- Managed Services: Leveraging cloud provider security features for databases and application hosting.
- Production-Ready Containers: Optimizing Docker images for security and efficiency.
Tech Stack
For this deployment milestone, we will leverage a combination of our existing stack and specific Azure services:
- Python (v3.13): The primary language for our Flask application.
- Flask (v3.x): The web framework.
- PostgreSQL (v16): Our relational database.
- Docker: For containerizing our application, ensuring consistent environments.
- Azure CLI: The command-line interface for interacting with Azure services.
- Azure App Service (Web App for Containers): A fully managed platform for running containerized web applications. Chosen for its ease of deployment, scalability, and integration with other Azure security features.
- Azure Database for PostgreSQL (Flexible Server): A managed database service that handles patching, backups, and high availability, reducing operational overhead and enhancing security compared to self-hosting.
📌 Key Idea: Using managed cloud services like Azure App Service and Azure Database for PostgreSQL offloads significant operational and security responsibilities to the cloud provider, allowing developers to focus more on application-level security.
Milestones: Cloud Deployment Plan
To ensure a structured and verifiable deployment, we’ll follow these steps:
- Prerequisites Setup: Install and configure necessary tools (Azure CLI, Docker).
- Container Image Hardening: Optimize the
Dockerfilefor production, including multi-stage builds and non-root users. - Image Build & Push: Build the production Docker image and push it to a container registry (Docker Hub).
- Azure Resource Provisioning: Create an Azure Resource Group, a managed PostgreSQL Flexible Server, and an Azure App Service Plan and Web App.
- Secure Configuration: Configure Azure App Service application settings to inject sensitive environment variables into the Flask container.
- Application Code Update: Modify the Flask application to read configuration from environment variables.
- Network Security Hardening: Restrict PostgreSQL firewall access to only the Azure App Service outbound IPs.
- Verification: Test the deployed application’s functionality and inspect security headers.
Planning & Design: Cloud Architecture
Deploying a web application securely to the cloud requires a thoughtful approach to infrastructure, configuration, and secrets management. Our goal is to host our Dockerized Flask application and its PostgreSQL database on Azure, focusing on minimizing attack surface and using platform features for security.
Deployment Architecture Overview
The core idea is to separate our application code from sensitive configurations and data. The Flask application, running in a Docker container, will fetch its database credentials and other secrets from environment variables provided by the App Service. The App Service itself will connect to the managed PostgreSQL instance over a secure channel.
Key Security Principles for Cloud Deployment
- Principle of Least Privilege: Configure Azure resources with only the necessary permissions. For instance, the App Service will have specific network access to the PostgreSQL database, and the database user will have minimal privileges.
- Secrets Management: Avoid hardcoding sensitive information. We’ll use Azure App Service’s application settings, which inject values as environment variables into the container. For more advanced scenarios, Azure Key Vault would be integrated using Managed Identities.
- Network Security: Utilize cloud-native networking features like firewall rules and Virtual Network (VNet) integration to restrict access to our application and database.
- Container Security: Ensure our Docker image is production-ready, running as a non-root user and minimizing unnecessary components.
- Defense in Depth: Implement multiple layers of security controls, from the network edge to the application code, to protect against various attack vectors.
Step-by-Step Implementation
This section guides you through preparing your application for cloud deployment and configuring the necessary Azure resources.
1. Prerequisites Setup
Ensure you have the following installed and configured. Always refer to official documentation for the most up-to-date installation instructions.
- Azure CLI (latest stable): Used to interact with Azure services from your terminal.
- Docker Desktop (or equivalent container runtime, latest stable): To build and push your Docker image.
- Official docs: https://docs.docker.com/desktop/install/
- Docker Hub Account: Or another container registry (e.g., Azure Container Registry). We’ll use Docker Hub for simplicity.
- Sign up at: https://hub.docker.com/
Authenticate your Azure CLI:
az login2. Container Image Hardening for Production
We need to optimize our Dockerfile for production environments. This includes using a multi-stage build to reduce image size and running the application as a non-root user to minimize potential container escape vulnerabilities.
File: Dockerfile
# Stage 1: Build dependencies
# Using python:3.13-slim-bookworm, a lightweight base image.
# As of 2026-07-13, Python 3.13 is a reasonable expectation for a recent stable version.
# Always verify the latest stable Python release when building.
FROM python:3.13-slim-bookworm AS builder
# Set environment variables for non-interactive operations and Python specifics
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
# Set the working directory inside the container
WORKDIR /app
# Install build dependencies required for psycopg2 (PostgreSQL client)
# --no-install-recommends reduces the number of installed packages
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Stage 2: Create final runtime image
# Use the same slim base image for consistency and minimal footprint
FROM python:3.13-slim-bookworm
# Set the working directory
WORKDIR /app
# Copy only runtime dependencies from the builder stage
# This significantly reduces the final image size and attack surface
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn
# Copy the application code
COPY . .
# Create a dedicated non-root user for running the application
# This adheres to the principle of least privilege, enhancing security
RUN adduser --system --group appuser
USER appuser
# Expose the port our Flask app will run on (Gunicorn default)
EXPOSE 8000
# Command to run the application using Gunicorn, binding to all interfaces
# Ensure gunicorn is listed in your requirements.txt
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]Explanation of changes:
- Multi-stage build: Separates build-time dependencies (like
build-essential) from runtime dependencies, resulting in a smaller, more secure final image. - Lightweight base image:
python:3.13-slim-bookworm(or the then-current stable version) is used to minimize the OS footprint. - Non-root user:
adduser --system --group appuser && USER appusercreates and switches to a dedicated userappuser. This is a critical security practice, as it prevents the application from having root privileges inside the container, limiting damage in case of a compromise. - Gunicorn: Used as the production-grade WSGI server. Make sure
gunicornis included in yourrequirements.txt. - Environment variables:
PYTHONUNBUFFEREDandPYTHONDONTWRITEBYTECODEare set for better logging and performance in containers.
Ensure your requirements.txt includes gunicorn and psycopg2-binary (or psycopg2 if you prefer to build it).
File: requirements.txt (partial example)
Flask==3.0.3
Werkzeug==3.0.1
psycopg2-binary==2.9.9
gunicorn==22.0.0
python-dotenv==1.0.1
... (other dependencies)As of 2026-07-13, these are hypothetical versions based on current stable releases. Always verify the latest stable versions for your project.
3. Build and Push Your Docker Image
Now, build your production-optimized Docker image and push it to Docker Hub. Replace YOUR_DOCKERHUB_USERNAME and your-secure-flask-app with your actual username and desired image name.
docker build -t YOUR_DOCKERHUB_USERNAME/your-secure-flask-app:latest .
docker push YOUR_DOCKERHUB_USERNAME/your-secure-flask-app:latestThis makes your container image available for Azure App Service to pull.
4. Azure Resource Provisioning
We’ll use the Azure CLI to provision the necessary services.
4.1. Create a Resource Group
A resource group is a logical container for your Azure resources.
RESOURCE_GROUP_NAME="SecureFlaskRG"
LOCATION="eastus" # Choose an Azure region close to you for lower latency
az group create --name $RESOURCE_GROUP_NAME --location $LOCATION4.2. Create Azure Database for PostgreSQL - Flexible Server
This command sets up a managed PostgreSQL instance. For production, always use a strong, unique password.
POSTGRES_SERVER_NAME="secure-flask-db-$(head /dev/urandom | tr -dc a-z0-9 | head -c 8)" # Unique name
POSTGRES_ADMIN_USER="dbadmin"
POSTGRES_ADMIN_PASSWORD="YourStrongPassword123!" # <--- REPLACE with a STRONG, COMPLEX password!
az postgres flexible-server create \
--resource-group $RESOURCE_GROUP_NAME \
--name $POSTGRES_SERVER_NAME \
--location $LOCATION \
--version "16" \
--sku-name Standard_D2ds_v4 \
--tier Burstable \
--storage-size 32 \
--storage-iops 500 \
--admin-user $POSTGRES_ADMIN_USER \
--admin-password $POSTGRES_ADMIN_PASSWORD \
--public-access 0.0.0.0 --action Add # Temporarily enable public access for initial setup. We will restrict this immediately after deployment.🧠 Important: For production, never use --public-access 0.0.0.0 --action Add as a permanent solution. This exposes your database to the entire internet. We use it here temporarily for initial setup simplicity, but the critical next step is to restrict access. The best practice for production is to use Azure Virtual Network (VNet) integration for your App Service and place the PostgreSQL server within the VNet, or use Private Link for private connectivity.
4.3. Create Azure App Service Plan and Web App
An App Service Plan defines the underlying compute resources. The Web App then runs on this plan.
APP_PLAN_NAME="SecureFlaskPlan"
WEB_APP_NAME="secure-flask-app-$(head /dev/urandom | tr -dc a-z0-9 | head -c 8)" # Unique name
DOCKER_IMAGE="YOUR_DOCKERHUB_USERNAME/your-secure-flask-app:latest"
# Create App Service Plan (Basic tier for tutorial, use Premium V2/V3 for production)
az appservice plan create \
--resource-group $RESOURCE_GROUP_NAME \
--name $APP_PLAN_NAME \
--location $LOCATION \
--is-linux \
--sku B1
# Create Web App for Containers, pulling from your Docker Hub image
az webapp create \
--resource-group $RESOURCE_GROUP_NAME \
--plan $APP_PLAN_NAME \
--name $WEB_APP_NAME \
--deployment-container-image-name $DOCKER_IMAGE \
--output json5. Secure Configuration with Application Settings
This is a critical step for secure configuration. We’ll set environment variables for our Flask application, including the database connection string and SECRET_KEY, using Azure App Service’s application settings. These settings are securely injected into your container at runtime.
First, retrieve the PostgreSQL connection string. You’ll need the server name, admin user, and the strong password you set earlier.
# Get PostgreSQL server hostname
POSTGRES_HOST=$(az postgres flexible-server show --resource-group $RESOURCE_GROUP_NAME --name $POSTGRES_SERVER_NAME --query "fullyQualifiedDomainName" --output tsv)
# Construct the connection string. Replace POSTGRES_ADMIN_PASSWORD with your actual password.
# The default database for Azure Flexible Server is 'postgres'.
DB_CONNECTION_STRING="postgresql://${POSTGRES_ADMIN_USER}:${POSTGRES_ADMIN_PASSWORD}@${POSTGRES_HOST}:5432/postgres"Now, set the application settings on your Azure Web App.
# Generate a new, strong SECRET_KEY for production. Never hardcode this.
FLASK_SECRET_KEY=$(python -c 'import secrets; print(secrets.token_hex(32))')
az webapp config appsettings set \
--resource-group $RESOURCE_GROUP_NAME \
--name $WEB_APP_NAME \
--settings \
DATABASE_URL="$DB_CONNECTION_STRING" \
FLASK_ENV="production" \
SECRET_KEY="$FLASK_SECRET_KEY" \
DEBUG="False" \
PORT="8000" # Important for Gunicorn running on port 8000 inside containerExplanation:
DATABASE_URL: Our Flask application will read this to connect to PostgreSQL.FLASK_ENV="production": Ensures Flask runs in production mode.SECRET_KEY: A randomly generated, strong key crucial for session security and other cryptographic operations. Never hardcode this or commit it to version control.DEBUG="False": Disables debug mode in production to prevent sensitive information leaks.PORT="8000": Azure App Service needs to know which port your container is listening on. Since Gunicorn defaults to 8000, we specify this.
6. Update Flask Application to Use Environment Variables
Ensure your Flask application reads these environment variables for configuration. This makes your application adaptable to different environments without code changes.
File: app.py (or config.py if you have one)
import os
from flask import Flask, session, request, jsonify
from datetime import timedelta
from werkzeug.security import generate_password_hash, check_password_hash
import psycopg2
from psycopg2 import extras
from functools import wraps
# --- Configuration using Environment Variables ---
# Use os.getenv() to fetch environment variables.
# Provide insecure default values for local development to prevent accidental production use.
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://dev_user:dev_pass@localhost:5432/dev_db")
SECRET_KEY = os.getenv("SECRET_KEY", "INSECURE_DEV_KEY_CHANGE_ME_IN_PROD_IMMEDIATELY")
FLASK_ENV = os.getenv("FLASK_ENV", "development")
DEBUG_MODE = os.getenv("DEBUG", "True").lower() == "true"
app = Flask(__name__)
# --- Flask App Configuration ---
app.config["SECRET_KEY"] = SECRET_KEY
app.config["SESSION_COOKIE_SECURE"] = (FLASK_ENV == "production") # Only send cookie over HTTPS in production
app.config["SESSION_COOKIE_HTTPONLY"] = True # Prevent client-side script access to cookie
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # CSRF protection (consider "Strict" for higher security)
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=30) # Session expiry
app.debug = DEBUG_MODE
app.env = FLASK_ENV
# Database connection function
def get_db_connection():
"""Establishes a connection to the PostgreSQL database."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
print(f"Database connection error: {e}")
# In production, log this error but avoid exposing details to the user
raise ConnectionError("Could not connect to the database.")
# Example: Initialize database (run once on deployment or use a separate migration tool)
def init_db():
"""Initializes the database schema if tables do not exist."""
try:
conn = get_db_connection()
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(80) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL
);
""")
conn.commit()
cur.close()
conn.close()
print("Database initialized successfully.")
except Exception as e:
print(f"Error initializing database: {e}")
# Log this error for debugging but don't stop the app on startup in prod
# if the table already exists (e.g., in subsequent deployments)
# Call init_db on application startup.
# In a real-world scenario, you'd use a dedicated migration tool (e.g., Alembic).
with app.app_context():
init_db()
# --- Authentication Decorator (from previous chapter) ---
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'username' not in session:
return jsonify({"message": "Unauthorized"}), 401
return f(*args, **kwargs)
return decorated_function
# --- Routes (from previous chapter) ---
@app.route('/')
def index():
return "Welcome to the Secure Flask App! Access /register, /login, or /protected."
@app.route('/register', methods=['POST'])
def register():
username = request.json.get('username')
password = request.json.get('password')
if not username or not password:
return jsonify({"message": "Username and password are required"}), 400
# Basic input validation
if len(username) < 3 or len(password) < 8:
return jsonify({"message": "Username must be at least 3 chars, password at least 8"}), 400
hashed_password = generate_password_hash(password, method='pbkdf2:sha256') # Use a strong hashing algorithm
conn = None
try:
conn = get_db_connection()
cur = conn.cursor()
cur.execute("INSERT INTO users (username, password_hash) VALUES (%s, %s)", (username, hashed_password))
conn.commit()
cur.close()
conn.close()
return jsonify({"message": "User registered successfully"}), 201
except psycopg2.IntegrityError:
return jsonify({"message": "Username already exists"}), 409
except Exception as e:
print(f"Registration error: {e}")
return jsonify({"message": "An error occurred during registration"}), 500
finally:
if conn:
conn.close()
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
if not username or not password:
return jsonify({"message": "Username and password are required"}), 400
conn = None
try:
conn = get_db_connection()
cur = conn.cursor(cursor_factory=extras.DictCursor)
cur.execute("SELECT id, username, password_hash FROM users WHERE username = %s", (username,))
user = cur.fetchone()
cur.close()
conn.close()
if user and check_password_hash(user['password_hash'], password):
session['username'] = user['username']
session['user_id'] = user['id']
return jsonify({"message": "Logged in successfully", "username": user['username']}), 200
else:
return jsonify({"message": "Invalid credentials"}), 401
except Exception as e:
print(f"Login error: {e}")
return jsonify({"message": "An error occurred during login"}), 500
finally:
if conn:
conn.close()
@app.route('/logout', methods=['POST'])
@login_required
def logout():
session.pop('username', None)
session.pop('user_id', None)
return jsonify({"message": "Logged out successfully"}), 200
@app.route('/protected', methods=['GET'])
@login_required
def protected_route():
return jsonify({"message": f"Hello, {session['username']}! This is a protected resource."}), 200
# --- Error Handling ---
@app.errorhandler(404)
def not_found_error(error):
return jsonify({"message": "Resource not found"}), 404
@app.errorhandler(500)
def internal_error(error):
# In production, do not expose internal error details
app.logger.error('Server Error: %s', (error))
return jsonify({"message": "An unexpected server error occurred"}), 500
if __name__ == '__main__':
# When running locally, you can pass environment variables like this:
# DATABASE_URL="postgresql://user:password@localhost:5432/yourdb" SECRET_KEY="dev_key" python app.py
app.run(host='0.0.0.0', port=8000) # Ensure this matches EXPOSE and PORT env var in AzureExplanation of changes:
os.getenv(): All sensitive and environment-specific configurations (database URL, secret key, debug mode, environment type) are now read from environment variables usingos.getenv().- Insecure Defaults: For local development, default values are provided to make the application runnable. These defaults are deliberately insecure (e.g.,
INSECURE_DEV_KEY_CHANGE_ME_IN_PROD_IMMEDIATELY) to prevent accidental use in production. SESSION_COOKIE_SECURE: This is conditionally set toTrueonly whenFLASK_ENVis “production,” ensuring the session cookie is only sent over HTTPS.init_db()call: Theinit_db()function is called within anapp.app_context()block to ensure it runs on application startup. In a larger project, a dedicated database migration tool (like Alembic) would manage schema changes.- Error Handling: The
500error handler is updated to log the error but return a generic message to the user, preventing information leakage. PORTenvironment variable: Azure App Service will set aPORTenvironment variable that Gunicorn will respect. While Gunicorn’s default--bind 0.0.0.0:8000is often sufficient, explicitly settingPORT=8000in Azure App Service settings (as done in step 5) is a good practice for clarity.
7. Network Security Hardening: PostgreSQL Firewall
After confirming your application is deployed, immediately update your PostgreSQL firewall rules to allow access only from your Azure App Service. This is a critical security measure to protect your database from unauthorized access.
First, get the outbound IP addresses of your App Service:
az webapp show --resource-group $RESOURCE_GROUP_NAME --name $WEB_APP_NAME --query outboundIpAddresses --output tsvThis command returns a comma-separated list of IP addresses. You’ll need to add these as firewall rules to your PostgreSQL server.
# Get current firewall rules (optional, to verify what's there)
az postgres flexible-server firewall list --resource-group $RESOURCE_GROUP_NAME --name $POSTGRES_SERVER_NAME --output json
# Remove the broad public access rule (if you added it in step 4.2)
# Replace 'AllowAllPublicAccess' with the actual rule name if it differs.
az postgres flexible-server firewall delete \
--resource-group $RESOURCE_GROUP_NAME \
--name $POSTGRES_SERVER_NAME \
--rule-name AllowAllPublicAccess
# Add a rule for your App Service's outbound IP(s).
# Replace <APP_SERVICE_OUTBOUND_IP> with the actual IP address you retrieved.
# If the 'az webapp show' command returned multiple IPs, repeat the 'firewall create' command
# for each IP, giving each rule a unique --rule-name (e.g., AllowWebAppAccess1, AllowWebAppAccess2).
# Example for a single IP:
az postgres flexible-server firewall create \
--resource-group $RESOURCE_GROUP_NAME \
--name $POSTGRES_SERVER_NAME \
--rule-name AllowWebAppAccess1 \
--start-ip-address <APP_SERVICE_OUTBOUND_IP> \
--end-ip-address <APP_SERVICE_OUTBOUND_IP>⚠️ What can go wrong: Failing to restrict database access is one of the most common and severe security vulnerabilities. An attacker could potentially connect directly to your database if it’s exposed to the internet, bypassing your application’s security controls.
Testing & Verification
Once the deployment commands are complete, it may take a few minutes for the App Service to pull the Docker image and start the application.
Get the Web App URL:
az webapp show --resource-group $RESOURCE_GROUP_NAME --name $WEB_APP_NAME --query defaultHostName --output tsvThis will output a URL like
secure-flask-app-xxxxxx.azurewebsites.net.Access the Application: Open the URL in your web browser. You should see your Flask application’s welcome page.
Test Functionality:
- User Registration: Try to register a new user via
POSTrequest to/register. This verifies the application can connect to the PostgreSQL database and write data. - User Login: Log in with the newly created user via
POSTrequest to/login. This tests authentication and session management. - Authenticated Endpoints: Access the
/protectedAPI endpoint after logging in. - Error Handling: Try to trigger an expected error (e.g., duplicate username registration) and observe that no sensitive information is leaked in the response (generic 409 or 500 message).
- User Registration: Try to register a new user via
Inspect Session Cookies:
- Using your browser’s developer tools (e.g., “Application” tab in Chrome, “Storage” in Firefox), inspect the session cookie.
- Verify that the
SecureandHttpOnlyflags are set. TheSecureflag ensures the cookie is only sent over HTTPS, andHttpOnlyprevents client-side JavaScript access.
Check Azure Logs:
- In the Azure portal, navigate to your App Service.
- Under “Monitoring,” click “Log stream” or “App Service logs” to view application logs and diagnose any startup issues or connection errors. This is your first line of defense for debugging deployed applications.
Verify PostgreSQL Firewall:
- Attempt to connect to your PostgreSQL database directly from your local machine using
psqlor a database client. If you have correctly configured the firewall rules, this connection attempt should fail, as your local IP is not allowed. This confirms the database is isolated.
- Attempt to connect to your PostgreSQL database directly from your local machine using
Common Issues & Solutions
- “Application Error” or 500 Internal Server Error:
- Cause: The container failed to start, or your Flask app crashed during initialization.
- Solution: Check the Azure App Service “Log stream” in the Azure portal. Look for Python tracebacks or Gunicorn errors. Common culprits include incorrect environment variables (
DATABASE_URL,SECRET_KEY), missingrequirements.txtdependencies, or issues with yourCMDin theDockerfile. Ensure thePORTenvironment variable is set to8000in App Service settings.
- Cannot connect to PostgreSQL database:
- Cause: Incorrect
DATABASE_URL, PostgreSQL server not running, or firewall blocking access. - Solution:
- Verify the
DATABASE_URLin App Service settings matches the PostgreSQL server’s details (hostname, user, password). - Confirm the PostgreSQL server is running in Azure.
- Crucially: Check your PostgreSQL flexible server’s firewall rules. Ensure the outbound IP addresses of your App Service are explicitly allowed and that any broad public access rules have been removed.
- Verify the
- Cause: Incorrect
SECRET_KEYerror or session issues:- Cause:
SECRET_KEYnot set as an environment variable in App Service, or mismatch between local and deployed configuration. - Solution: Double-check that
SECRET_KEYis set in your App Service application settings. Ensure your Flask app reads it correctly usingos.getenv(). Also, verifySESSION_COOKIE_SECUREisTruein production and your site is accessed via HTTPS.
- Cause:
Summary & Next Step
You’ve successfully deployed your secure Flask application to Azure, configured it with environment variables, and integrated it with a managed PostgreSQL database. This is a significant step towards building production-ready, secure applications. You’ve also hardened the environment by restricting database access and using production-grade container images. This hands-on experience demonstrates the practical application of secure configuration and cloud network principles.
What’s next? With your application running in a cloud environment, the next logical step is to perform more advanced security testing and establish continuous security practices. This includes vulnerability scanning, penetration testing simulations, and integrating security into your development pipeline to maintain a strong security posture over time.
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.
References
- Azure CLI Documentation: https://learn.microsoft.com/en-us/cli/azure/
- Azure App Service Documentation: https://learn.microsoft.com/en-us/azure/app-service/
- Azure Database for PostgreSQL - Flexible Server: https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/
- Deploy secure applications on Microsoft Azure | Microsoft Learn: https://learn.microsoft.com/en-us/azure/security/develop/secure-deploy
- Docker Documentation: https://docs.docker.com/
- Python
os.getenv()documentation: https://docs.python.org/3/library/os.html#os.getenv - Flask Configuration Best Practices: https://flask.palletsprojects.com/en/3.0/config/ +++