Python
Koala’s Minimal python image built on 0-deb through distroless design significantly reduces CVEs in the final image. The python image is production-ready image optimized for python workloads.
Koala’s python image is available at multiple registries, including dockerhub regsitry & koala’s public ecr .
Koala’s Python demo flask app
Use the following command to download the koala python image locally:
docker pull koalalab/python
This guide helps in setting a sample flask demo for building a full application on top of koala’s minimal python image.
Start by making a Koala Demo folder
mkdir Koala-Demo
cd Koala-Demo
Save the following file as demo.yml
services:
web:
image: public.ecr.aws/b7m2q1o2/flask-demo
ports:
- "0.0.0.0:5001:5001"
environment:
- DB_HOST=db
depends_on:
- db
restart: unless-stopped
db:
image: postgres:17
environment:
- POSTGRES_DB=flask_app
- POSTGRES_USER=flask_user
- POSTGRES_PASSWORD=your_actual_password
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
pg_data:
In your terminal, run the following commands
brew install docker-compose
docker-compose -f demo.yml up -d
After this you should be able to access flask demo app at http://localhost:5001
& would look something like this:

Editing & running the flask demo
The above was a sample demo created by KoalaLab.
If you want to build the image locally & run it yourself, here are the steps:
-
Create a local dockerfile, save this as flask.dockerfile
ARG VENV_NAME=koala
# Multi-arch build using koalalab/python images
FROM koalalab/python:latest-builder AS builder
ARG VENV_NAME
RUN python3 -m venv $VENV_NAME
ENV PATH="$WORKDIR/$VENV_NAME/bin:$PATH"
COPY flask/requirements.txt flask/requirements.txt
RUN pip install --no-cache-dir -r flask/requirements.txt
COPY flask flask
FROM koalalab/python
ARG VENV_NAME
COPY --from=builder $WORKDIR $WORKDIR
ENV PATH="$WORKDIR/$VENV_NAME/bin:$PATH"
# Expose Flask port
EXPOSE 5001
CMD ["python3", "flask/app.py"]
2. Create a sub-folder called flask
mkdir flask
cd flask
3. Create two files in the flask folder
-
app.py
-
requirements.txt
4. Edit the python file app.py include the following code
from flask import Flask, request, jsonify, render_template_string
import psycopg2
from psycopg2.extras import RealDictCursor
import os
from datetime import datetime
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
# Database configuration
DATABASE_CONFIG = {
'host': os.getenv('DB_HOST', 'localhost'),
'database': os.getenv('DB_NAME', 'flask_app'),
'user': os.getenv('DB_USER', 'flask_user'),
'password': os.getenv('PASSWORD', 'your_actual_password'),
'port': os.getenv('DB_PORT', '5432')
}
def get_db_connection():
"""Create and return a database connection"""
try:
conn = psycopg2.connect(**DATABASE_CONFIG)
return conn
except psycopg2.Error as e:
logger.error(f"Database connection error: {e}")
return None
def init_database():
"""Initialize the database with required tables"""
conn = get_db_connection()
if not conn:
logger.error("Could not connect to database for initialization")
return False
try:
with conn.cursor() as cursor:
# Create users table
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
logger.info("Database initialized successfully")
return True
except psycopg2.Error as e:
logger.error(f"Database initialization error: {e}")
conn.rollback()
return False
finally:
conn.close()
# HTML template for the web interface
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>Flask PostgreSQL App</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.form-group { margin: 10px 0; }
input, button { padding: 8px; margin: 5px; }
button { background: #007bff; color: white; border: none; cursor: pointer; }
button:hover { background: #0056b3; }
.user { background: #f8f9fa; padding: 10px; margin: 5px 0; border-radius: 5px; }
.error { color: red; }
.success { color: green; }
</style>
</head>
<body>
<h1>Flask PostgreSQL User Management</h1>
<div id="message"></div>
<h2>Add New User</h2>
<form id="userForm">
<div class="form-group">
<input type="text" id="name" placeholder="Name" required>
</div>
<div class="form-group">
<input type="email" id="email" placeholder="Email" required>
</div>
<button type="submit">Add User</button>
</form>
<h2>Users</h2>
<div id="users"></div>
<button onclick="loadUsers()">Refresh Users</button>
<script>
function showMessage(text, isError = false) {
const messageDiv = document.getElementById('message');
messageDiv.textContent = text;
messageDiv.className = isError ? 'error' : 'success';
setTimeout(() => messageDiv.textContent = '', 3000);
}
document.getElementById('userForm').addEventListener('submit', async (e) => {
e.preventDefault();
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email })
});
const data = await response.json();
if (response.ok) {
showMessage('User added successfully!');
document.getElementById('userForm').reset();
loadUsers();
} else {
showMessage(data.error || 'Error adding user', true);
}
} catch (error) {
showMessage('Network error', true);
}
});
async function loadUsers() {
try {
const response = await fetch('/api/users');
const users = await response.json();
const usersDiv = document.getElementById('users');
if (users.length === 0) {
usersDiv.innerHTML = '<p>No users found</p>';
} else {
usersDiv.innerHTML = users.map(user =>
`<div class="user">
<strong>${user.name}</strong> - ${user.email}
<br><small>Created: ${new Date(user.created_at).toLocaleString()}</small>
<button onclick="deleteUser(${user.id})" style="float: right; background: #dc3545;">Delete</button>
</div>`
).join('');
}
} catch (error) {
showMessage('Error loading users', true);
}
}
async function deleteUser(userId) {
if (!confirm('Are you sure you want to delete this user?')) return;
try {
const response = await fetch(`/api/users/${userId}`, { method: 'DELETE' });
const data = await response.json();
if (response.ok) {
showMessage('User deleted successfully!');
loadUsers();
} else {
showMessage(data.error || 'Error deleting user', true);
}
} catch (error) {
showMessage('Network error', true);
}
}
// Load users on page load
loadUsers();
</script>
</body>
</html>
"""
@app.route('/')
def index():
"""Serve the main page"""
return render_template_string(HTML_TEMPLATE)
@app.route('/health')
def health_check():
"""Health check endpoint"""
conn = get_db_connection()
if conn:
conn.close()
return jsonify({'status': 'healthy', 'database': 'connected'})
else:
return jsonify({'status': 'unhealthy', 'database': 'disconnected'}), 500
@app.route('/api/users', methods=['GET'])
def get_users():
"""Get all users"""
conn = get_db_connection()
if not conn:
return jsonify({'error': 'Database connection failed'}), 500
try:
with conn.cursor(cursor_factory=RealDictCursor) as cursor:
cursor.execute("SELECT * FROM users ORDER BY created_at DESC")
users = cursor.fetchall()
return jsonify([dict(user) for user in users])
except psycopg2.Error as e:
logger.error(f"Error fetching users: {e}")
return jsonify({'error': 'Failed to fetch users'}), 500
finally:
conn.close()
@app.route('/api/users', methods=['POST'])
def create_user():
"""Create a new user"""
data = request.get_json()
if not data or not data.get('name') or not data.get('email'):
return jsonify({'error': 'Name and email are required'}), 400
conn = get_db_connection()
if not conn:
return jsonify({'error': 'Database connection failed'}), 500
try:
with conn.cursor(cursor_factory=RealDictCursor) as cursor:
cursor.execute(
"INSERT INTO users (name, email) VALUES (%s, %s) RETURNING *",
(data['name'], data['email'])
)
user = cursor.fetchone()
conn.commit()
return jsonify(dict(user)), 201
except psycopg2.IntegrityError:
conn.rollback()
return jsonify({'error': 'Email already exists'}), 409
except psycopg2.Error as e:
logger.error(f"Error creating user: {e}")
conn.rollback()
return jsonify({'error': 'Failed to create user'}), 500
finally:
conn.close()
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
"""Get a specific user"""
conn = get_db_connection()
if not conn:
return jsonify({'error': 'Database connection failed'}), 500
try:
with conn.cursor(cursor_factory=RealDictCursor) as cursor:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
user = cursor.fetchone()
if not user:
return jsonify({'error': 'User not found'}), 404
return jsonify(dict(user))
except psycopg2.Error as e:
logger.error(f"Error fetching user: {e}")
return jsonify({'error': 'Failed to fetch user'}), 500
finally:
conn.close()
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
"""Delete a user"""
conn = get_db_connection()
if not conn:
return jsonify({'error': 'Database connection failed'}), 500
try:
with conn.cursor() as cursor:
cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
if cursor.rowcount == 0:
return jsonify({'error': 'User not found'}), 404
conn.commit()
return jsonify({'message': 'User deleted successfully'})
except psycopg2.Error as e:
logger.error(f"Error deleting user: {e}")
conn.rollback()
return jsonify({'error': 'Failed to delete user'}), 500
finally:
conn.close()
@app.errorhandler(404)
def not_found(error):
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({'error': 'Internal server error'}), 500
if __name__ == '__main__':
# Initialize database on startup
if init_database():
logger.info("Starting Flask application...")
app.run(debug=True, host='0.0.0.0', port=5001)
else:
logger.error("Failed to initialize database. Exiting.")
exit(1)
5. Edit the requirements.txt file to save the following code
Flask==3.0.0
psycopg2-binary==2.9.10
python-dotenv==1.0.0
6. Come back to previous folder
cd ..
7. Edit line 3 of demo.yml to the following line
Remove this
image: public.ecr.aws/b7m2q1o2/flask-demo
to
Change to:
image: local-flask-demo
8. Run the following command in terminal
docker build -t local-flask-demo -f flask.dockerfile .
9. Run the demo through this terminal command
docker-compose -f demo.yml up -d
After this you should be able to access the local flask demo app at http://localhost:5001