In our previous tutorial, we learned how to set up PgAdmin and PostgreSQL using Docker containers, providing a convenient and portable database management environment. Building on that foundation, this tutorial takes the next step by integrating PostgreSQL with a Flask web application and adding rich text editing capabilities using CKEditor. This tutorial demonstrates a real-world use case that further enhances the value of the PgAdmin and PostgreSQL setup from the previous tutorial.
Prerequisite:
This tutorial is part of the Flask CKEditor Project Series.
📚 View the Complete Flask CKEditor Series
Preliminary:
I need to set up the file and folder structure, as below: Everything is as in the previous tutorial, except that I will create a new "docker-compose.yaml", "dockerfile" and "requirements.txt" file. Since this tutorial uses PostgreSQL as the database, I also amended both app.py and database.py.
I have listed all the dependencies in requirements.txt, and therefore, it will install all of them.
What are those files?
1) docker-compose.yaml—a configuration file used to define and run multi-container Docker applications. In my use case, I have 3 services, including PostgreSQL, PgAdmin, and a Flask app.
2) dockerfile—a text document containing all the commands a user could call on the command line to assemble a single Docker image. In my use case, I have described how the Flask installation works.
3) requirements.txt—lists all the external packages (and their specific versions) that I need to run. I will discuss it later on.
What are the scenarios for running a Flask app with PostgreSQL?
(a) Run on a local computer without Docker.
In fact, I can run any Flask app with PostgreSQL without Docker; simply download PgAdmin and PostgreSQL in the desktop version, and they work well.
(b) Run on Docker and isolated from the local computer
Since I have discussed Docker with PgAdmin and PostgreSQL in the last tutorial, I will further discuss how to use it with a Flask app.
Step 1: Launch Docker Desktop and configure the relevant file
First and foremost, I need to launch Docker Desktop on my local computer, and then I will configure the following file.
(a) docker-compose.yaml
services:
postgres:
image: postgres:16
container_name: postgres_db
restart: always
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin123
ports:
- "8080:80"
flask:
build: .
container_name: flask_app
restart: always
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgresql://admin:secret@postgres:5432/mydb
depends_on:
- postgres
volumes:
postgres_data: {}
In the previous tutorial, I discussed PostgreSQL and PgAdmin, while in this tutorial, I simply added Flask to the same YAML file. Since Docker is separated from my local computer, if the Flask app runs outside the Docker container, the Flask app will not be able to access PostgreSQL. Therefore, I need to include the same Docker to facilitate communication between them. (b) Dockerfile
The above Dockerfile describes step-by-step how to set up a Flask image, the working path, where to locate the dependencies file, and the exposed port.# 1. Use an official lightweight Python image
FROM python:3.11-slim
# 2. Set the working directory inside the container
WORKDIR /app
# 3. Copy the requirements file first (helps with Docker caching)
COPY requirements.txt .
# 4. Install the Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# 5. Copy the rest of your Flask application code
COPY . .
# 6. Expose the port Flask runs on
EXPOSE 5000
# 7. Command to run the application (using Gunicorn for production)
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"](c) Requirements.txt
Flask
Flask-WTF
Flask-CKEditor
psycopg2-binary
WTForms
email-validator
gunicornIt is one of the requirements (#4) in the Dockerfile; therefore, I have listed all the Python packages to install on Docker. To connect to PostgreSQL, I have to install psycopg2-binary.Step 2: Amend database.py and app.py
Once I have installed psycopg2-binary, I will connect them both in database.py and app.py. All the content remains the same, except for the following:
(a) database.py
import os
import psycopg2
import time
def init_db():
DATABASE_URL = os.environ.get(
'DATABASE_URL',
'postgresql://admin:secret@postgres:5432/mydb'
)
# Retry mechanism: Give Postgres a few seconds to fully start up inside Docker
for i in range(5):
try:
# Connect to PostgreSQL URL
conn = psycopg2.connect(DATABASE_URL)
c = conn.cursor()
# Create table query
c.execute("""
CREATE TABLE IF NOT EXISTS message (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
subscribe BOOLEAN DEFAULT FALSE,
message TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
""")
conn.commit()
c.close()
conn.close()
break
except psycopg2.OperationalError as e:
print(f"Database not ready yet (attempt {i+1}/5), waiting...")
time.sleep(3)I have created an init_db() function; this will call it up again in the app.py. This is how I connect the database.py and app.py together.
The column is as follows:
(i) id - series, automatically created
(ii) name - varchar, from the web form
(iii) email - varchar, from the web form
(iv) subscribe - boolean, from the web form
(v) message - text, from the web form
(vi) timestamp - timestamp, automatically created
If the connection fails, it will restart 5 times until the database is connected.
(b) app.py
Step 3: Dockerise the Flask app, PostgreSQL and PgAdminfrom flask import (Flask, render_template, redirect,
url_for)
from database import init_db
import psycopg2
init_db()
# Define the route for handling contact form submissions (supports both GET
# and POST requests)
@app.route('/message', methods=['GET', 'POST'])
def submit():
# Instantiate the Flask-WTF contact form
form = ContactForm()
# Check if the request is a POST request and if all form validation
# rules pass
if form.validate_on_submit():
try:
print("Testing database connection...")
# Establish a connection to the PostgreSQL database container
conn = psycopg2.connect(
host="postgres", # Name of the PostgreSQL service/container
port=5432, # Default PostgreSQL port
user='admin', # Database username
password='secret', # Database password
dbname="mydb" # Target database name
)
print("Connected!")
# Create a cursor object to execute SQL commands
c = conn.cursor()
# Parameterized SQL query to prevent SQL injection vulnerabilities
query = """
INSERT INTO message (name, email, subscribe, message)
VALUES (%s, %s, %s, %s)
"""
# Map form data into a tuple matching the query parameters
# PostgreSQL requires an explicit boolean type, handled here by bool()
data = (
form.name.data,
form.email.data,
bool(form.subscribe.data),
form.message.data
)
# Execute the query with the sanitized data
c.execute(query, data)
# Commit the transaction to save changes permanently to the database
conn.commit()
# Clean up and close database resources
c.close()
conn.close()
# Redirect the user to the 'thankyou' endpoint upon successful
# submission
return redirect(url_for('thankyou'))
except Exception as e:
# Capture and print the full error traceback for easier debugging
import traceback
print(traceback.format_exc())
# If it's a GET request or form validation fails, render the initial
# form page
return render_template('contact.html', form=form)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
Above is an excerpt from app.py. Since the 'index' and 'thankyou' functions remain the same, they will not be displayed here. Under the templates folder, the contacts.html and thankyou.html also remain intact; please refer to the previous tutorial on CKEditor on SQLite. PostgreSQL connection
Since I had installed psycopg2-binary; it is used to connect to the PostgreSQL database. My host is 'postgre', which is the service name in the 'docker-compose.yaml' file. The default port is 5432, while the user name, password, and dbname are all defined in the 'docker-compose.yaml' file. Please refer to the PostgreSQL service in the 'docker-compose.yaml' file above.
The query
The major difference between SQLite and PostgreSQL is their way of querying.
(i) SQLite
query = """INSERT INTO message (name, email, subscribe, message)
VALUES (:name, :email, :subscribe, :message)"""
(ii) PostgreSQL
query = """INSERT INTO message (name, email, subscribe, message)
VALUES (%s, %s, %s, %s)"""
Initilisation of init()
I had imported init() from database.py and executed this initialisation function in app.py. It does not need to run separately.
Host and port
In the previous tutorial, the Flask app's host and port are always set to host='127.0.0.1' and port=5000; therefore, the Flask application will only listen for connections originating from the same computer it is running on.
However, when I use Docker, it is isolated from my internal network and has its own separate localhost (127.0.0.1), and therefore my browser is unable to communicate with the Flask app. As the chain breaks at the Docker Container boundary, the request gets dropped. Therefore, my browser will indicate an error "Connection Refused."
To avoid the above error, I need to configure host='0.0.0.0' and port=5000. The Flask app inside the container: "Listen for traffic coming from anywhere, including outside this container". This allows Docker's port forwarding (e.g., -p 5000:5000) to successfully pass traffic from my actual physical machine's browser straight into the Flask app. It is up and running as usual.
Once all has been set, it is now time to run the Docker container; simply, under the Bash terminal, input the Docker command line as follows:
The following terminal displayed
Now go ahead and open 2 browsers
docker-compose up --build(a) Configure PgAdmin
Once I have logged in to PgAdmin, let me register the server.
(i) General tab
(b) Test-run the Flask app and check the record in PgAdmin
Once logged into PgAdmin, go to the side menu under the database, click mydb, then under the top menu "Tools," click the drop-down menu "Query Tool," or on the keyboard, hold the "Alt + Shift + Q" to call out the PgAdmin command box. Next, input this command line: "SELECT * FROM public.message ORDER BY id ASC" and click the "Execute Query" button on the top menu bar, and the table will display the current row of data. Then, go ahead to my Flask app, fill out all the necessary fields, including name, email, and message text; select the "subscribe" option; and click the submit button. The Flask app will be redirected to the thank-you page to indicate the submission was successful.
Now, return to PgAdmin; once again, click the same button, and the table will update with a new row of data that I just submitted.
Final wrap-up:
In this tutorial, we combined Flask, CKEditor, PostgreSQL, PgAdmin, and Docker to create a practical web application that supports rich text content management and database storage. We learned how to connect a Flask application to a PostgreSQL database running in Docker, manage data through PgAdmin, and integrate CKEditor to provide a user-friendly editing experience. This project demonstrates how these technologies work together to build a modern, scalable, and maintainable application.Published: June 2026
Last Updated: June 2026
Last Updated: June 2026
About the Author
Kelvin Loh is a Python developer focused on Flask, desktop applications, and business automation solutions. He shares practical tutorials and real-world coding projects to help developers and small businesses build useful applications.









Comments
Post a Comment