[{"data":1,"prerenderedAt":1718},["ShallowReactive",2],{"\u002Fdeploy\u002Fflask-plus-postgresql-production-setup":3},{"id":4,"title":5,"body":6,"description":1708,"extension":1709,"meta":1710,"navigation":109,"path":1714,"seo":1715,"stem":1716,"__hash__":1717},"content\u002Fdeploy\u002Fflask-plus-postgresql-production-setup.md","Flask + PostgreSQL Production Setup",{"type":7,"value":8,"toc":1697},"minimark",[9,13,17,22,25,246,253,257,263,267,790,804,811,838,845,860,863,903,910,977,984,987,1057,1064,1100,1107,1116,1127,1144,1148,1152,1237,1241,1244,1304,1307,1361,1364,1399,1402,1416,1419,1446,1449,1481,1484,1507,1519,1523,1595,1602,1606,1630,1634,1642,1653,1664,1672,1683,1687,1693],[10,11,5],"h1",{"id":12},"flask-postgresql-production-setup",[14,15,16],"p",{},"If you're trying to run Flask with PostgreSQL in production, this guide shows you how to configure the database, connect your app safely, and validate that the deployment works. Use it to replace SQLite or development settings with a production-ready PostgreSQL setup.",[18,19,21],"h2",{"id":20},"quick-fix-quick-setup","Quick Fix \u002F Quick Setup",[14,23,24],{},"Use this for the fastest working path when PostgreSQL runs on the same host as Flask:",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-bash shiki shiki-themes github-light github-dark","sudo -u postgres psql \u003C\u003C'SQL'\nCREATE DATABASE flaskapp_prod;\nCREATE USER flaskapp WITH PASSWORD 'change-this-password';\nALTER ROLE flaskapp SET client_encoding TO 'utf8';\nALTER ROLE flaskapp SET default_transaction_isolation TO 'read committed';\nALTER ROLE flaskapp SET timezone TO 'UTC';\nGRANT ALL PRIVILEGES ON DATABASE flaskapp_prod TO flaskapp;\nSQL\n\npython -m venv .venv\nsource .venv\u002Fbin\u002Factivate\npip install flask gunicorn psycopg2-binary flask-sqlalchemy flask-migrate\n\nexport DATABASE_URL='postgresql:\u002F\u002Fflaskapp:change-this-password@127.0.0.1:5432\u002Fflaskapp_prod'\nflask db upgrade\n\npython - \u003C\u003C'PY'\nimport os\nfrom sqlalchemy import create_engine, text\nengine = create_engine(os.environ['DATABASE_URL'])\nwith engine.connect() as conn:\n    print(conn.execute(text('SELECT 1')).scalar())\nPY\n","bash","",[33,34,35,62,68,74,80,86,92,98,104,111,126,135,159,164,180,192,197,210,216,222,228,234,240],"code",{"__ignoreMap":31},[36,37,40,44,48,52,55,59],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"sScJk","sudo",[36,45,47],{"class":46},"sj4cs"," -u",[36,49,51],{"class":50},"sZZnC"," postgres",[36,53,54],{"class":50}," psql",[36,56,58],{"class":57},"szBVR"," \u003C\u003C",[36,60,61],{"class":50},"'SQL'\n",[36,63,65],{"class":38,"line":64},2,[36,66,67],{"class":50},"CREATE DATABASE flaskapp_prod;\n",[36,69,71],{"class":38,"line":70},3,[36,72,73],{"class":50},"CREATE USER flaskapp WITH PASSWORD 'change-this-password';\n",[36,75,77],{"class":38,"line":76},4,[36,78,79],{"class":50},"ALTER ROLE flaskapp SET client_encoding TO 'utf8';\n",[36,81,83],{"class":38,"line":82},5,[36,84,85],{"class":50},"ALTER ROLE flaskapp SET default_transaction_isolation TO 'read committed';\n",[36,87,89],{"class":38,"line":88},6,[36,90,91],{"class":50},"ALTER ROLE flaskapp SET timezone TO 'UTC';\n",[36,93,95],{"class":38,"line":94},7,[36,96,97],{"class":50},"GRANT ALL PRIVILEGES ON DATABASE flaskapp_prod TO flaskapp;\n",[36,99,101],{"class":38,"line":100},8,[36,102,103],{"class":50},"SQL\n",[36,105,107],{"class":38,"line":106},9,[36,108,110],{"emptyLinePlaceholder":109},true,"\n",[36,112,114,117,120,123],{"class":38,"line":113},10,[36,115,116],{"class":42},"python",[36,118,119],{"class":46}," -m",[36,121,122],{"class":50}," venv",[36,124,125],{"class":50}," .venv\n",[36,127,129,132],{"class":38,"line":128},11,[36,130,131],{"class":46},"source",[36,133,134],{"class":50}," .venv\u002Fbin\u002Factivate\n",[36,136,138,141,144,147,150,153,156],{"class":38,"line":137},12,[36,139,140],{"class":42},"pip",[36,142,143],{"class":50}," install",[36,145,146],{"class":50}," flask",[36,148,149],{"class":50}," gunicorn",[36,151,152],{"class":50}," psycopg2-binary",[36,154,155],{"class":50}," flask-sqlalchemy",[36,157,158],{"class":50}," flask-migrate\n",[36,160,162],{"class":38,"line":161},13,[36,163,110],{"emptyLinePlaceholder":109},[36,165,167,170,174,177],{"class":38,"line":166},14,[36,168,169],{"class":57},"export",[36,171,173],{"class":172},"sVt8B"," DATABASE_URL",[36,175,176],{"class":57},"=",[36,178,179],{"class":50},"'postgresql:\u002F\u002Fflaskapp:change-this-password@127.0.0.1:5432\u002Fflaskapp_prod'\n",[36,181,183,186,189],{"class":38,"line":182},15,[36,184,185],{"class":42},"flask",[36,187,188],{"class":50}," db",[36,190,191],{"class":50}," upgrade\n",[36,193,195],{"class":38,"line":194},16,[36,196,110],{"emptyLinePlaceholder":109},[36,198,200,202,205,207],{"class":38,"line":199},17,[36,201,116],{"class":42},[36,203,204],{"class":50}," -",[36,206,58],{"class":57},[36,208,209],{"class":50},"'PY'\n",[36,211,213],{"class":38,"line":212},18,[36,214,215],{"class":50},"import os\n",[36,217,219],{"class":38,"line":218},19,[36,220,221],{"class":50},"from sqlalchemy import create_engine, text\n",[36,223,225],{"class":38,"line":224},20,[36,226,227],{"class":50},"engine = create_engine(os.environ['DATABASE_URL'])\n",[36,229,231],{"class":38,"line":230},21,[36,232,233],{"class":50},"with engine.connect() as conn:\n",[36,235,237],{"class":38,"line":236},22,[36,238,239],{"class":50},"    print(conn.execute(text('SELECT 1')).scalar())\n",[36,241,243],{"class":38,"line":242},23,[36,244,245],{"class":50},"PY\n",[14,247,248,249,252],{},"Replace the password, move ",[33,250,251],{},"DATABASE_URL"," into a persistent environment file, and validate the same environment is loaded by systemd before sending traffic.",[18,254,256],{"id":255},"whats-happening","What’s Happening",[14,258,259,260,262],{},"PostgreSQL requires a valid connection URL, a real database user, correct privileges, and schema migrations that match the application code. Most production failures come from one of four places: invalid ",[33,261,251],{},", authentication problems, missing PostgreSQL driver, or migrations not applied. If Flask starts but fails on requests, check both the app service environment and PostgreSQL logs.",[18,264,266],{"id":265},"step-by-step-guide","Step-by-Step Guide",[268,269,270,312,349,402,454,480,506,527,744,785],"ol",{},[271,272,273,277],"li",{},[274,275,276],"strong",{},"Install PostgreSQL server and client packages",[26,278,280],{"className":28,"code":279,"language":30,"meta":31,"style":31},"sudo apt update\nsudo apt install -y postgresql postgresql-contrib libpq-dev\n",[33,281,282,292],{"__ignoreMap":31},[36,283,284,286,289],{"class":38,"line":39},[36,285,43],{"class":42},[36,287,288],{"class":50}," apt",[36,290,291],{"class":50}," update\n",[36,293,294,296,298,300,303,306,309],{"class":38,"line":64},[36,295,43],{"class":42},[36,297,288],{"class":50},[36,299,143],{"class":50},[36,301,302],{"class":46}," -y",[36,304,305],{"class":50}," postgresql",[36,307,308],{"class":50}," postgresql-contrib",[36,310,311],{"class":50}," libpq-dev\n",[271,313,314,317],{},[274,315,316],{},"Enable and start PostgreSQL",[26,318,320],{"className":28,"code":319,"language":30,"meta":31,"style":31},"sudo systemctl enable --now postgresql\nsudo systemctl status postgresql\n",[33,321,322,338],{"__ignoreMap":31},[36,323,324,326,329,332,335],{"class":38,"line":39},[36,325,43],{"class":42},[36,327,328],{"class":50}," systemctl",[36,330,331],{"class":50}," enable",[36,333,334],{"class":46}," --now",[36,336,337],{"class":50}," postgresql\n",[36,339,340,342,344,347],{"class":38,"line":64},[36,341,43],{"class":42},[36,343,328],{"class":50},[36,345,346],{"class":50}," status",[36,348,337],{"class":50},[271,350,351,354],{},[274,352,353],{},"Create a production database and dedicated database user",[26,355,357],{"className":28,"code":356,"language":30,"meta":31,"style":31},"sudo -u postgres psql \u003C\u003C'SQL'\nCREATE DATABASE flaskapp_prod;\nCREATE USER flaskapp WITH PASSWORD 'strong-password';\nALTER ROLE flaskapp SET client_encoding TO 'utf8';\nALTER ROLE flaskapp SET default_transaction_isolation TO 'read committed';\nALTER ROLE flaskapp SET timezone TO 'UTC';\nGRANT ALL PRIVILEGES ON DATABASE flaskapp_prod TO flaskapp;\nSQL\n",[33,358,359,373,377,382,386,390,394,398],{"__ignoreMap":31},[36,360,361,363,365,367,369,371],{"class":38,"line":39},[36,362,43],{"class":42},[36,364,47],{"class":46},[36,366,51],{"class":50},[36,368,54],{"class":50},[36,370,58],{"class":57},[36,372,61],{"class":50},[36,374,375],{"class":38,"line":64},[36,376,67],{"class":50},[36,378,379],{"class":38,"line":70},[36,380,381],{"class":50},"CREATE USER flaskapp WITH PASSWORD 'strong-password';\n",[36,383,384],{"class":38,"line":76},[36,385,79],{"class":50},[36,387,388],{"class":38,"line":82},[36,389,85],{"class":50},[36,391,392],{"class":38,"line":88},[36,393,91],{"class":50},[36,395,396],{"class":38,"line":94},[36,397,97],{"class":50},[36,399,400],{"class":38,"line":100},[36,401,103],{"class":50},[271,403,404,407,410,411,414,415],{},[274,405,406],{},"If schema creation fails later, grant schema privileges",[408,409],"br",{},"Some deployments also require explicit access to the ",[33,412,413],{},"public"," schema:",[26,416,418],{"className":28,"code":417,"language":30,"meta":31,"style":31},"sudo -u postgres psql -d flaskapp_prod \u003C\u003C'SQL'\nGRANT ALL ON SCHEMA public TO flaskapp;\nALTER SCHEMA public OWNER TO flaskapp;\nSQL\n",[33,419,420,440,445,450],{"__ignoreMap":31},[36,421,422,424,426,428,430,433,436,438],{"class":38,"line":39},[36,423,43],{"class":42},[36,425,47],{"class":46},[36,427,51],{"class":50},[36,429,54],{"class":50},[36,431,432],{"class":46}," -d",[36,434,435],{"class":50}," flaskapp_prod",[36,437,58],{"class":57},[36,439,61],{"class":50},[36,441,442],{"class":38,"line":64},[36,443,444],{"class":50},"GRANT ALL ON SCHEMA public TO flaskapp;\n",[36,446,447],{"class":38,"line":70},[36,448,449],{"class":50},"ALTER SCHEMA public OWNER TO flaskapp;\n",[36,451,452],{"class":38,"line":76},[36,453,103],{"class":50},[271,455,456,459],{},[274,457,458],{},"Create or activate the application virtual environment",[26,460,462],{"className":28,"code":461,"language":30,"meta":31,"style":31},"python -m venv .venv\nsource .venv\u002Fbin\u002Factivate\n",[33,463,464,474],{"__ignoreMap":31},[36,465,466,468,470,472],{"class":38,"line":39},[36,467,116],{"class":42},[36,469,119],{"class":46},[36,471,122],{"class":50},[36,473,125],{"class":50},[36,475,476,478],{"class":38,"line":64},[36,477,131],{"class":46},[36,479,134],{"class":50},[271,481,482,485],{},[274,483,484],{},"Install Flask and PostgreSQL dependencies",[26,486,488],{"className":28,"code":487,"language":30,"meta":31,"style":31},"pip install flask gunicorn psycopg2-binary flask-sqlalchemy flask-migrate\n",[33,489,490],{"__ignoreMap":31},[36,491,492,494,496,498,500,502,504],{"class":38,"line":39},[36,493,140],{"class":42},[36,495,143],{"class":50},[36,497,146],{"class":50},[36,499,149],{"class":50},[36,501,152],{"class":50},[36,503,155],{"class":50},[36,505,158],{"class":50},[271,507,508,511],{},[274,509,510],{},"Set the production database URL",[26,512,514],{"className":28,"code":513,"language":30,"meta":31,"style":31},"export DATABASE_URL='postgresql:\u002F\u002Fflaskapp:strong-password@127.0.0.1:5432\u002Fflaskapp_prod'\n",[33,515,516],{"__ignoreMap":31},[36,517,518,520,522,524],{"class":38,"line":39},[36,519,169],{"class":57},[36,521,173],{"class":172},[36,523,176],{"class":57},[36,525,526],{"class":50},"'postgresql:\u002F\u002Fflaskapp:strong-password@127.0.0.1:5432\u002Fflaskapp_prod'\n",[271,528,529,532,534,535,580,582,583],{},[274,530,531],{},"Configure Flask to read the database URL from the environment",[408,533],{},"Example configuration:",[26,536,539],{"className":537,"code":538,"language":116,"meta":31,"style":31},"language-python shiki shiki-themes github-light github-dark","import os\n\nSQLALCHEMY_DATABASE_URI = os.environ[\"DATABASE_URL\"]\nSQLALCHEMY_TRACK_MODIFICATIONS = False\n",[33,540,541,549,553,570],{"__ignoreMap":31},[36,542,543,546],{"class":38,"line":39},[36,544,545],{"class":57},"import",[36,547,548],{"class":172}," os\n",[36,550,551],{"class":38,"line":64},[36,552,110],{"emptyLinePlaceholder":109},[36,554,555,558,561,564,567],{"class":38,"line":70},[36,556,557],{"class":46},"SQLALCHEMY_DATABASE_URI",[36,559,560],{"class":57}," =",[36,562,563],{"class":172}," os.environ[",[36,565,566],{"class":50},"\"DATABASE_URL\"",[36,568,569],{"class":172},"]\n",[36,571,572,575,577],{"class":38,"line":76},[36,573,574],{"class":46},"SQLALCHEMY_TRACK_MODIFICATIONS",[36,576,560],{"class":57},[36,578,579],{"class":46}," False\n",[408,581],{},"If using an app factory:",[26,584,586],{"className":537,"code":585,"language":116,"meta":31,"style":31},"import os\nfrom flask import Flask\nfrom flask_sqlalchemy import SQLAlchemy\nfrom flask_migrate import Migrate\n\ndb = SQLAlchemy()\nmigrate = Migrate()\n\ndef create_app():\n    app = Flask(__name__)\n    app.config[\"SQLALCHEMY_DATABASE_URI\"] = os.environ[\"DATABASE_URL\"]\n    app.config[\"SQLALCHEMY_TRACK_MODIFICATIONS\"] = False\n\n    db.init_app(app)\n    migrate.init_app(app, db)\n\n    return app\n",[33,587,588,594,607,619,631,635,645,655,659,670,686,705,718,722,727,732,736],{"__ignoreMap":31},[36,589,590,592],{"class":38,"line":39},[36,591,545],{"class":57},[36,593,548],{"class":172},[36,595,596,599,602,604],{"class":38,"line":64},[36,597,598],{"class":57},"from",[36,600,601],{"class":172}," flask ",[36,603,545],{"class":57},[36,605,606],{"class":172}," Flask\n",[36,608,609,611,614,616],{"class":38,"line":70},[36,610,598],{"class":57},[36,612,613],{"class":172}," flask_sqlalchemy ",[36,615,545],{"class":57},[36,617,618],{"class":172}," SQLAlchemy\n",[36,620,621,623,626,628],{"class":38,"line":76},[36,622,598],{"class":57},[36,624,625],{"class":172}," flask_migrate ",[36,627,545],{"class":57},[36,629,630],{"class":172}," Migrate\n",[36,632,633],{"class":38,"line":82},[36,634,110],{"emptyLinePlaceholder":109},[36,636,637,640,642],{"class":38,"line":88},[36,638,639],{"class":172},"db ",[36,641,176],{"class":57},[36,643,644],{"class":172}," SQLAlchemy()\n",[36,646,647,650,652],{"class":38,"line":94},[36,648,649],{"class":172},"migrate ",[36,651,176],{"class":57},[36,653,654],{"class":172}," Migrate()\n",[36,656,657],{"class":38,"line":100},[36,658,110],{"emptyLinePlaceholder":109},[36,660,661,664,667],{"class":38,"line":106},[36,662,663],{"class":57},"def",[36,665,666],{"class":42}," create_app",[36,668,669],{"class":172},"():\n",[36,671,672,675,677,680,683],{"class":38,"line":113},[36,673,674],{"class":172},"    app ",[36,676,176],{"class":57},[36,678,679],{"class":172}," Flask(",[36,681,682],{"class":46},"__name__",[36,684,685],{"class":172},")\n",[36,687,688,691,694,697,699,701,703],{"class":38,"line":128},[36,689,690],{"class":172},"    app.config[",[36,692,693],{"class":50},"\"SQLALCHEMY_DATABASE_URI\"",[36,695,696],{"class":172},"] ",[36,698,176],{"class":57},[36,700,563],{"class":172},[36,702,566],{"class":50},[36,704,569],{"class":172},[36,706,707,709,712,714,716],{"class":38,"line":137},[36,708,690],{"class":172},[36,710,711],{"class":50},"\"SQLALCHEMY_TRACK_MODIFICATIONS\"",[36,713,696],{"class":172},[36,715,176],{"class":57},[36,717,579],{"class":46},[36,719,720],{"class":38,"line":161},[36,721,110],{"emptyLinePlaceholder":109},[36,723,724],{"class":38,"line":166},[36,725,726],{"class":172},"    db.init_app(app)\n",[36,728,729],{"class":38,"line":182},[36,730,731],{"class":172},"    migrate.init_app(app, db)\n",[36,733,734],{"class":38,"line":194},[36,735,110],{"emptyLinePlaceholder":109},[36,737,738,741],{"class":38,"line":199},[36,739,740],{"class":57},"    return",[36,742,743],{"class":172}," app\n",[271,745,746,749,766,768,769],{},[274,747,748],{},"Set the Flask app target for CLI commands if required",[26,750,752],{"className":28,"code":751,"language":30,"meta":31,"style":31},"export FLASK_APP='wsgi.py'\n",[33,753,754],{"__ignoreMap":31},[36,755,756,758,761,763],{"class":38,"line":39},[36,757,169],{"class":57},[36,759,760],{"class":172}," FLASK_APP",[36,762,176],{"class":57},[36,764,765],{"class":50},"'wsgi.py'\n",[408,767],{},"Or for a factory-based app:",[26,770,772],{"className":28,"code":771,"language":30,"meta":31,"style":31},"export FLASK_APP='myapp:create_app()'\n",[33,773,774],{"__ignoreMap":31},[36,775,776,778,780,782],{"class":38,"line":39},[36,777,169],{"class":57},[36,779,760],{"class":172},[36,781,176],{"class":57},[36,783,784],{"class":50},"'myapp:create_app()'\n",[271,786,787],{},[274,788,789],{},"Initialize migrations if the project does not already include them",[26,791,793],{"className":28,"code":792,"language":30,"meta":31,"style":31},"flask db init\n",[33,794,795],{"__ignoreMap":31},[36,796,797,799,801],{"class":38,"line":39},[36,798,185],{"class":42},[36,800,188],{"class":50},[36,802,803],{"class":50}," init\n",[268,805,806],{"start":128},[271,807,808],{},[274,809,810],{},"Generate and apply schema migrations",[26,812,814],{"className":28,"code":813,"language":30,"meta":31,"style":31},"flask db migrate -m 'initial'\nflask db upgrade\n",[33,815,816,830],{"__ignoreMap":31},[36,817,818,820,822,825,827],{"class":38,"line":39},[36,819,185],{"class":42},[36,821,188],{"class":50},[36,823,824],{"class":50}," migrate",[36,826,119],{"class":46},[36,828,829],{"class":50}," 'initial'\n",[36,831,832,834,836],{"class":38,"line":64},[36,833,185],{"class":42},[36,835,188],{"class":50},[36,837,191],{"class":50},[268,839,840],{"start":137},[271,841,842],{},[274,843,844],{},"Test direct database connectivity from the same environment",[26,846,848],{"className":28,"code":847,"language":30,"meta":31,"style":31},"python -c \"import os, psycopg2; conn = psycopg2.connect(os.environ['DATABASE_URL']); print('ok'); conn.close()\"\n",[33,849,850],{"__ignoreMap":31},[36,851,852,854,857],{"class":38,"line":39},[36,853,116],{"class":42},[36,855,856],{"class":46}," -c",[36,858,859],{"class":50}," \"import os, psycopg2; conn = psycopg2.connect(os.environ['DATABASE_URL']); print('ok'); conn.close()\"\n",[14,861,862],{},"Or with SQLAlchemy:",[26,864,866],{"className":28,"code":865,"language":30,"meta":31,"style":31},"python - \u003C\u003C'PY'\nimport os\nfrom sqlalchemy import create_engine, text\nengine = create_engine(os.environ['DATABASE_URL'])\nwith engine.connect() as conn:\n    print(conn.execute(text('SELECT current_database(), current_user')).fetchone())\nPY\n",[33,867,868,878,882,886,890,894,899],{"__ignoreMap":31},[36,869,870,872,874,876],{"class":38,"line":39},[36,871,116],{"class":42},[36,873,204],{"class":50},[36,875,58],{"class":57},[36,877,209],{"class":50},[36,879,880],{"class":38,"line":64},[36,881,215],{"class":50},[36,883,884],{"class":38,"line":70},[36,885,221],{"class":50},[36,887,888],{"class":38,"line":76},[36,889,227],{"class":50},[36,891,892],{"class":38,"line":82},[36,893,233],{"class":50},[36,895,896],{"class":38,"line":88},[36,897,898],{"class":50},"    print(conn.execute(text('SELECT current_database(), current_user')).fetchone())\n",[36,900,901],{"class":38,"line":94},[36,902,245],{"class":50},[268,904,905],{"start":161},[271,906,907],{},[274,908,909],{},"Move secrets into a persistent environment file for systemd",[26,911,913],{"className":28,"code":912,"language":30,"meta":31,"style":31},"sudo mkdir -p \u002Fetc\u002Fflask\nsudo tee \u002Fetc\u002Fflask\u002Fmyapp.env > \u002Fdev\u002Fnull \u003C\u003C'EOF'\nDATABASE_URL=postgresql:\u002F\u002Fflaskapp:strong-password@127.0.0.1:5432\u002Fflaskapp_prod\nFLASK_ENV=production\nEOF\nsudo chmod 600 \u002Fetc\u002Fflask\u002Fmyapp.env\n",[33,914,915,928,949,954,959,964],{"__ignoreMap":31},[36,916,917,919,922,925],{"class":38,"line":39},[36,918,43],{"class":42},[36,920,921],{"class":50}," mkdir",[36,923,924],{"class":46}," -p",[36,926,927],{"class":50}," \u002Fetc\u002Fflask\n",[36,929,930,932,935,938,941,944,946],{"class":38,"line":64},[36,931,43],{"class":42},[36,933,934],{"class":50}," tee",[36,936,937],{"class":50}," \u002Fetc\u002Fflask\u002Fmyapp.env",[36,939,940],{"class":57}," >",[36,942,943],{"class":50}," \u002Fdev\u002Fnull",[36,945,58],{"class":57},[36,947,948],{"class":50},"'EOF'\n",[36,950,951],{"class":38,"line":70},[36,952,953],{"class":50},"DATABASE_URL=postgresql:\u002F\u002Fflaskapp:strong-password@127.0.0.1:5432\u002Fflaskapp_prod\n",[36,955,956],{"class":38,"line":76},[36,957,958],{"class":50},"FLASK_ENV=production\n",[36,960,961],{"class":38,"line":82},[36,962,963],{"class":50},"EOF\n",[36,965,966,968,971,974],{"class":38,"line":88},[36,967,43],{"class":42},[36,969,970],{"class":50}," chmod",[36,972,973],{"class":46}," 600",[36,975,976],{"class":50}," \u002Fetc\u002Fflask\u002Fmyapp.env\n",[268,978,979],{"start":166},[271,980,981],{},[274,982,983],{},"Reference the environment file in the Gunicorn systemd unit",[14,985,986],{},"Example unit:",[26,988,992],{"className":989,"code":990,"language":991,"meta":31,"style":31},"language-ini shiki shiki-themes github-light github-dark","[Unit]\nDescription=Gunicorn instance for myapp\nAfter=network.target\n\n[Service]\nUser=www-data\nGroup=www-data\nWorkingDirectory=\u002Fvar\u002Fwww\u002Fmyapp\nEnvironmentFile=\u002Fetc\u002Fflask\u002Fmyapp.env\nExecStart=\u002Fvar\u002Fwww\u002Fmyapp\u002F.venv\u002Fbin\u002Fgunicorn --workers 3 --bind unix:\u002Frun\u002Fmyapp.sock wsgi:app\n\n[Install]\nWantedBy=multi-user.target\n","ini",[33,993,994,999,1004,1009,1013,1018,1023,1028,1033,1038,1043,1047,1052],{"__ignoreMap":31},[36,995,996],{"class":38,"line":39},[36,997,998],{},"[Unit]\n",[36,1000,1001],{"class":38,"line":64},[36,1002,1003],{},"Description=Gunicorn instance for myapp\n",[36,1005,1006],{"class":38,"line":70},[36,1007,1008],{},"After=network.target\n",[36,1010,1011],{"class":38,"line":76},[36,1012,110],{"emptyLinePlaceholder":109},[36,1014,1015],{"class":38,"line":82},[36,1016,1017],{},"[Service]\n",[36,1019,1020],{"class":38,"line":88},[36,1021,1022],{},"User=www-data\n",[36,1024,1025],{"class":38,"line":94},[36,1026,1027],{},"Group=www-data\n",[36,1029,1030],{"class":38,"line":100},[36,1031,1032],{},"WorkingDirectory=\u002Fvar\u002Fwww\u002Fmyapp\n",[36,1034,1035],{"class":38,"line":106},[36,1036,1037],{},"EnvironmentFile=\u002Fetc\u002Fflask\u002Fmyapp.env\n",[36,1039,1040],{"class":38,"line":113},[36,1041,1042],{},"ExecStart=\u002Fvar\u002Fwww\u002Fmyapp\u002F.venv\u002Fbin\u002Fgunicorn --workers 3 --bind unix:\u002Frun\u002Fmyapp.sock wsgi:app\n",[36,1044,1045],{"class":38,"line":128},[36,1046,110],{"emptyLinePlaceholder":109},[36,1048,1049],{"class":38,"line":137},[36,1050,1051],{},"[Install]\n",[36,1053,1054],{"class":38,"line":161},[36,1055,1056],{},"WantedBy=multi-user.target\n",[268,1058,1059],{"start":182},[271,1060,1061],{},[274,1062,1063],{},"Reload systemd and restart the application service",[26,1065,1067],{"className":28,"code":1066,"language":30,"meta":31,"style":31},"sudo systemctl daemon-reload\nsudo systemctl restart myapp\nsudo systemctl status myapp\n",[33,1068,1069,1078,1090],{"__ignoreMap":31},[36,1070,1071,1073,1075],{"class":38,"line":39},[36,1072,43],{"class":42},[36,1074,328],{"class":50},[36,1076,1077],{"class":50}," daemon-reload\n",[36,1079,1080,1082,1084,1087],{"class":38,"line":64},[36,1081,43],{"class":42},[36,1083,328],{"class":50},[36,1085,1086],{"class":50}," restart",[36,1088,1089],{"class":50}," myapp\n",[36,1091,1092,1094,1096,1098],{"class":38,"line":70},[36,1093,43],{"class":42},[36,1095,328],{"class":50},[36,1097,346],{"class":50},[36,1099,1089],{"class":50},[268,1101,1102],{"start":194},[271,1103,1104],{},[274,1105,1106],{},"Validate requests through Gunicorn and Nginx",[14,1108,1109,1110,1115],{},"Load a route that touches the database, such as a login route, health endpoint, or model query route. If you have not set up Gunicorn and Nginx yet, use the main deployment flow in ",[1111,1112,1114],"a",{"href":1113},"\u002Fdeploy\u002Fdeploy-flask-with-nginx-plus-gunicorn-step-by-step-guide","Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)",".",[268,1117,1118],{"start":199},[271,1119,1120],{},[274,1121,1122,1123,1126],{},"Validate PostgreSQL access with ",[33,1124,1125],{},"psql"," using the same credentials",[26,1128,1130],{"className":28,"code":1129,"language":30,"meta":31,"style":31},"psql 'postgresql:\u002F\u002Fflaskapp:strong-password@127.0.0.1:5432\u002Fflaskapp_prod' -c 'SELECT current_database(), current_user;'\n",[33,1131,1132],{"__ignoreMap":31},[36,1133,1134,1136,1139,1141],{"class":38,"line":39},[36,1135,1125],{"class":42},[36,1137,1138],{"class":50}," 'postgresql:\u002F\u002Fflaskapp:strong-password@127.0.0.1:5432\u002Fflaskapp_prod'",[36,1140,856],{"class":46},[36,1142,1143],{"class":50}," 'SELECT current_database(), current_user;'\n",[268,1145,1146],{"start":212},[271,1147],{},[18,1149,1151],{"id":1150},"common-causes","Common Causes",[1153,1154,1155,1165,1173,1183,1193,1205,1219,1231],"ul",{},[271,1156,1157,1160,1161,1164],{},[274,1158,1159],{},"Wrong connection string format"," → Flask cannot connect or parses invalid settings → use ",[33,1162,1163],{},"postgresql:\u002F\u002Fuser:password@host:5432\u002Fdbname"," and verify the exact value loaded by systemd.",[271,1166,1167,1170,1171,1115],{},[274,1168,1169],{},"PostgreSQL user lacks privileges"," → migrations or table creation fail → grant database privileges and schema privileges on ",[33,1172,413],{},[271,1174,1175,1178,1179,1182],{},[274,1176,1177],{},"Missing PostgreSQL driver"," → app crashes on import → install ",[33,1180,1181],{},"psycopg2-binary"," or another supported PostgreSQL driver in the same virtual environment Gunicorn uses.",[271,1184,1185,1188,1189,1192],{},[274,1186,1187],{},"Migrations not applied"," → app starts but fails on first query because tables are missing → run ",[33,1190,1191],{},"flask db upgrade"," in the production environment.",[271,1194,1195,1198,1199,1202,1203,1115],{},[274,1196,1197],{},"Local socket vs TCP mismatch"," → app tries a socket path while PostgreSQL expects TCP or vice versa → explicitly use ",[33,1200,1201],{},"127.0.0.1:5432"," in ",[33,1204,251],{},[271,1206,1207,1210,1211,1214,1215,1218],{},[274,1208,1209],{},"Environment variables not loaded under systemd"," → app works in shell but fails as a service → define ",[33,1212,1213],{},"EnvironmentFile="," or ",[33,1216,1217],{},"Environment="," in the service unit and restart it.",[271,1220,1221,1227,1228,1230],{},[274,1222,1223,1224],{},"Authentication method mismatch in ",[33,1225,1226],{},"pg_hba.conf"," → password auth fails even with correct credentials → review ",[33,1229,1226],{}," and reload PostgreSQL.",[271,1232,1233,1236],{},[274,1234,1235],{},"Firewall or remote-host restrictions"," → connection timeouts to a managed PostgreSQL instance → allow the host or network and verify listen and bind settings.",[18,1238,1240],{"id":1239},"debugging-section","Debugging Section",[14,1242,1243],{},"Check the application service:",[26,1245,1247],{"className":28,"code":1246,"language":30,"meta":31,"style":31},"sudo journalctl -u myapp -n 200 --no-pager\nsudo journalctl -u myapp -f\nsudo systemctl cat myapp\nsudo systemctl status myapp\n",[33,1248,1249,1270,1283,1294],{"__ignoreMap":31},[36,1250,1251,1253,1256,1258,1261,1264,1267],{"class":38,"line":39},[36,1252,43],{"class":42},[36,1254,1255],{"class":50}," journalctl",[36,1257,47],{"class":46},[36,1259,1260],{"class":50}," myapp",[36,1262,1263],{"class":46}," -n",[36,1265,1266],{"class":46}," 200",[36,1268,1269],{"class":46}," --no-pager\n",[36,1271,1272,1274,1276,1278,1280],{"class":38,"line":64},[36,1273,43],{"class":42},[36,1275,1255],{"class":50},[36,1277,47],{"class":46},[36,1279,1260],{"class":50},[36,1281,1282],{"class":46}," -f\n",[36,1284,1285,1287,1289,1292],{"class":38,"line":70},[36,1286,43],{"class":42},[36,1288,328],{"class":50},[36,1290,1291],{"class":50}," cat",[36,1293,1089],{"class":50},[36,1295,1296,1298,1300,1302],{"class":38,"line":76},[36,1297,43],{"class":42},[36,1299,328],{"class":50},[36,1301,346],{"class":50},[36,1303,1089],{"class":50},[14,1305,1306],{},"Check PostgreSQL:",[26,1308,1310],{"className":28,"code":1309,"language":30,"meta":31,"style":31},"sudo systemctl status postgresql\nsudo tail -n 200 \u002Fvar\u002Flog\u002Fpostgresql\u002Fpostgresql-*.log\nsudo ss -ltnp | grep 5432\n",[33,1311,1312,1322,1342],{"__ignoreMap":31},[36,1313,1314,1316,1318,1320],{"class":38,"line":39},[36,1315,43],{"class":42},[36,1317,328],{"class":50},[36,1319,346],{"class":50},[36,1321,337],{"class":50},[36,1323,1324,1326,1329,1331,1333,1336,1339],{"class":38,"line":64},[36,1325,43],{"class":42},[36,1327,1328],{"class":50}," tail",[36,1330,1263],{"class":46},[36,1332,1266],{"class":46},[36,1334,1335],{"class":50}," \u002Fvar\u002Flog\u002Fpostgresql\u002Fpostgresql-",[36,1337,1338],{"class":46},"*",[36,1340,1341],{"class":50},".log\n",[36,1343,1344,1346,1349,1352,1355,1358],{"class":38,"line":70},[36,1345,43],{"class":42},[36,1347,1348],{"class":50}," ss",[36,1350,1351],{"class":46}," -ltnp",[36,1353,1354],{"class":57}," |",[36,1356,1357],{"class":42}," grep",[36,1359,1360],{"class":46}," 5432\n",[14,1362,1363],{},"Inspect roles and databases:",[26,1365,1367],{"className":28,"code":1366,"language":30,"meta":31,"style":31},"sudo -u postgres psql -c '\\du'\nsudo -u postgres psql -c '\\l'\n",[33,1368,1369,1384],{"__ignoreMap":31},[36,1370,1371,1373,1375,1377,1379,1381],{"class":38,"line":39},[36,1372,43],{"class":42},[36,1374,47],{"class":46},[36,1376,51],{"class":50},[36,1378,54],{"class":50},[36,1380,856],{"class":46},[36,1382,1383],{"class":50}," '\\du'\n",[36,1385,1386,1388,1390,1392,1394,1396],{"class":38,"line":64},[36,1387,43],{"class":42},[36,1389,47],{"class":46},[36,1391,51],{"class":50},[36,1393,54],{"class":50},[36,1395,856],{"class":46},[36,1397,1398],{"class":50}," '\\l'\n",[14,1400,1401],{},"Test database login directly:",[26,1403,1404],{"className":28,"code":1129,"language":30,"meta":31,"style":31},[33,1405,1406],{"__ignoreMap":31},[36,1407,1408,1410,1412,1414],{"class":38,"line":39},[36,1409,1125],{"class":42},[36,1411,1138],{"class":50},[36,1413,856],{"class":46},[36,1415,1143],{"class":50},[14,1417,1418],{},"Test database access from the application environment:",[26,1420,1422],{"className":28,"code":1421,"language":30,"meta":31,"style":31},"source .venv\u002Fbin\u002Factivate\npython -c \"import os, psycopg2; conn = psycopg2.connect(os.environ['DATABASE_URL']); print('ok'); conn.close()\"\nflask db upgrade\n",[33,1423,1424,1430,1438],{"__ignoreMap":31},[36,1425,1426,1428],{"class":38,"line":39},[36,1427,131],{"class":46},[36,1429,134],{"class":50},[36,1431,1432,1434,1436],{"class":38,"line":64},[36,1433,116],{"class":42},[36,1435,856],{"class":46},[36,1437,859],{"class":50},[36,1439,1440,1442,1444],{"class":38,"line":70},[36,1441,185],{"class":42},[36,1443,188],{"class":50},[36,1445,191],{"class":50},[14,1447,1448],{},"What to look for:",[1153,1450,1451,1456,1461,1466,1471,1476],{},[271,1452,1453],{},[33,1454,1455],{},"password authentication failed",[271,1457,1458],{},[33,1459,1460],{},"database does not exist",[271,1462,1463],{},[33,1464,1465],{},"relation does not exist",[271,1467,1468],{},[33,1469,1470],{},"could not connect to server",[271,1472,1473],{},[33,1474,1475],{},"ModuleNotFoundError",[271,1477,1478],{},[33,1479,1480],{},"permission denied for schema public",[14,1482,1483],{},"If shell tests pass but the service fails, compare:",[1153,1485,1486,1489,1492,1497,1502],{},[271,1487,1488],{},"service user",[271,1490,1491],{},"virtual environment path",[271,1493,1494],{},[33,1495,1496],{},"WorkingDirectory",[271,1498,1499],{},[33,1500,1501],{},"EnvironmentFile",[271,1503,1504,1505],{},"loaded ",[33,1506,251],{},[14,1508,1509,1510,1514,1515,1115],{},"For environment loading and secret handling, see ",[1111,1511,1513],{"href":1512},"\u002Fdeploy\u002Fflask-environment-variables-and-secrets-setup","Flask Environment Variables and Secrets Setup",". For direct connection failures, see ",[1111,1516,1518],{"href":1517},"\u002Ffix-issues\u002Fflask-database-connection-errors-in-production","Flask Database Connection Errors in Production",[18,1520,1522],{"id":1521},"checklist","Checklist",[1153,1524,1527,1536,1542,1548,1557,1563,1569,1577,1583,1589],{"className":1525},[1526],"contains-task-list",[271,1528,1531,1535],{"className":1529},[1530],"task-list-item",[1532,1533],"input",{"disabled":109,"type":1534},"checkbox"," PostgreSQL is installed, enabled, and running",[271,1537,1539,1541],{"className":1538},[1530],[1532,1540],{"disabled":109,"type":1534}," A dedicated production database exists",[271,1543,1545,1547],{"className":1544},[1530],[1532,1546],{"disabled":109,"type":1534}," A dedicated database user exists with the correct password",[271,1549,1551,1553,1554,1556],{"className":1550},[1530],[1532,1552],{"disabled":109,"type":1534}," ",[33,1555,251],{}," points to the correct host, port, user, and database",[271,1558,1560,1562],{"className":1559},[1530],[1532,1561],{"disabled":109,"type":1534}," The PostgreSQL driver is installed in the active virtual environment",[271,1564,1566,1568],{"className":1565},[1530],[1532,1567],{"disabled":109,"type":1534}," Flask reads the database URL from environment variables",[271,1570,1572,1574,1575],{"className":1571},[1530],[1532,1573],{"disabled":109,"type":1534}," Migrations complete successfully with ",[33,1576,1191],{},[271,1578,1580,1582],{"className":1579},[1530],[1532,1581],{"disabled":109,"type":1534}," Gunicorn and systemd load the same environment used in manual tests",[271,1584,1586,1588],{"className":1585},[1530],[1532,1587],{"disabled":109,"type":1534}," Application routes that query the database return successful responses",[271,1590,1592,1594],{"className":1591},[1530],[1532,1593],{"disabled":109,"type":1534}," PostgreSQL and application logs show no authentication or schema errors after restart",[14,1596,1597,1598,1115],{},"For broader production validation, use ",[1111,1599,1601],{"href":1600},"\u002Fchecklist\u002Fflask-production-checklist-everything-you-must-do","Flask Production Checklist (Everything You Must Do)",[18,1603,1605],{"id":1604},"related-guides","Related Guides",[1153,1607,1608,1612,1616,1620,1626],{},[271,1609,1610],{},[1111,1611,1114],{"href":1113},[271,1613,1614],{},[1111,1615,1513],{"href":1512},[271,1617,1618],{},[1111,1619,1518],{"href":1517},[271,1621,1622],{},[1111,1623,1625],{"href":1624},"\u002Ffix-issues\u002Fflask-migrations-not-applied-fix-guide","Flask Migrations Not Applied (Fix Guide)",[271,1627,1628],{},[1111,1629,1601],{"href":1600},[18,1631,1633],{"id":1632},"faq","FAQ",[14,1635,1636,1639,1641],{},[274,1637,1638],{},"Q: Should production Flask use SQLite?",[408,1640],{},"\nNo. Use PostgreSQL or another production database for concurrency, durability, and operational safety.",[14,1643,1644,1650,1652],{},[274,1645,1646,1647,1649],{},"Q: Should I use ",[33,1648,1181],{}," in production?",[408,1651],{},"\nIt works for many deployments, but some teams prefer source-built drivers or psycopg3 packages for tighter control.",[14,1654,1655,1661,1663],{},[274,1656,1657,1658,1660],{},"Q: Where should ",[33,1659,251],{}," be stored?",[408,1662],{},"\nIn a systemd environment file, secret manager, or deployment platform configuration, not hardcoded in the repository.",[14,1665,1666,1669,1671],{},[274,1667,1668],{},"Q: Do I need migrations if tables already exist?",[408,1670],{},"\nYes, if your app uses Alembic or Flask-Migrate for schema control. Migration state must match the real database before future deploys.",[14,1673,1674,1677,1679,1680,1682],{},[274,1675,1676],{},"Q: Can PostgreSQL run on another host?",[408,1678],{},"\nYes. Update the host in ",[33,1681,251],{},", allow network access, and secure credentials, SSL settings, and firewall rules as required.",[18,1684,1686],{"id":1685},"final-takeaway","Final Takeaway",[14,1688,1689,1690,1692],{},"A production Flask + PostgreSQL setup depends on four things: a correct ",[33,1691,251],{},", an installed driver, applied migrations, and systemd or Gunicorn loading the same environment you tested manually. If shell tests pass but the app fails, check service environment loading, PostgreSQL authentication, and schema state first.",[1694,1695,1696],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":31,"searchDepth":64,"depth":64,"links":1698},[1699,1700,1701,1702,1703,1704,1705,1706,1707],{"id":20,"depth":64,"text":21},{"id":255,"depth":64,"text":256},{"id":265,"depth":64,"text":266},{"id":1150,"depth":64,"text":1151},{"id":1239,"depth":64,"text":1240},{"id":1521,"depth":64,"text":1522},{"id":1604,"depth":64,"text":1605},{"id":1632,"depth":64,"text":1633},{"id":1685,"depth":64,"text":1686},"Complete guide on flask + postgresql production setup for Flask production environments.","md",{"ogTitle":5,"ogDescription":1708,"twitterCard":1711,"robots":1712,"canonical":1713},"summary_large_image","index, follow","https:\u002F\u002Fflask-deployment.com\u002Fdeploy\u002Fflask-plus-postgresql-production-setup","\u002Fdeploy\u002Fflask-plus-postgresql-production-setup",{"title":5,"description":1708},"deploy\u002Fflask-plus-postgresql-production-setup","Sh8XZWr5wDNaC6ABJ_8O9h33Jx3kE7HcJzOCRdzbBeI",1776805765065]