App with Database¶
This guide walks through deploying an application with a PostgreSQL database. We will use the django-vue-notes sample app as the primary example, and touch on fastapi-todos for an alternative approach.
What You Get¶
When you set database: enabled: true in your appmecca.yaml, AppMecca:
- Provisions a managed PostgreSQL database for the app, on its first deploy
- Injects connection credentials as environment variables into your containers
- Handles database creation, backups, and restores through the CLI
Your app receives these environment variables automatically:
| Variable | Example Value |
|---|---|
DATABASE_URL |
postgresql://am_a1b2c3_user:…@<db-host>:5432/am_a1b2c3_db |
DB_HOST |
<db-host> |
DB_PORT |
5432 |
DB_NAME |
am_a1b2c3_db |
DB_USER |
am_a1b2c3_user |
DB_PASSWORD |
(auto-generated) |
Never hardcode the database or user name
The database and user names are opaque, generated identifiers — they are not
{app}_db / {app}_user, and they are not predictable from your app name. Always read
DATABASE_URL (or the individual DB_* variables) from the environment. Each branch of
a multi-branch app gets its own database, so main and dev never share data.
django-vue-notes (Django + PostgreSQL)¶
A note-taking app built with Django that stores notes in PostgreSQL.
appmecca.yaml¶
containers:
- name: django-vue-notes
dockerfile: Dockerfile
context: .
deploy:
- name: web
container: django-vue-notes
port: 8000
health_check:
path: /health/
database:
enabled: true
The only difference from a simple web app is the database: enabled: true line at the bottom.
Dockerfile¶
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# Install system dependencies for psycopg2
RUN apt-get update && \
apt-get install -y --no-install-recommends libpq-dev && \
rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Collect static files (with dummy SECRET_KEY for build step)
RUN SECRET_KEY=build-only python manage.py collectstatic --noinput
# Make entrypoint executable
RUN chmod +x entrypoint.sh
EXPOSE 8000
ENTRYPOINT ["./entrypoint.sh"]
psycopg2 system dependency
Django's PostgreSQL adapter (psycopg2) requires libpq-dev to be installed at the system level. Include this in your Dockerfile's RUN apt-get install step. Alternatively, use psycopg2-binary which bundles the library.
The Entrypoint Script¶
This is the key piece for database apps. The entrypoint.sh script runs before your application starts, handling database readiness and migrations:
#!/bin/bash
set -e
echo "==> Waiting for database..."
RETRIES=10
DELAY=3
for i in $(seq 1 $RETRIES); do
python -c "
import psycopg2
import os
try:
conn = psycopg2.connect(
host=os.environ.get('DB_HOST', 'localhost'),
port=os.environ.get('DB_PORT', '5432'),
dbname=os.environ.get('DB_NAME', 'app'),
user=os.environ.get('DB_USER', 'app'),
password=os.environ.get('DB_PASSWORD', ''),
)
conn.close()
print('Database is ready.')
except Exception as e:
print(f'Database not ready: {e}')
exit(1)
" && break
echo " Attempt $i/$RETRIES failed. Retrying in ${DELAY}s..."
sleep $DELAY
done
echo "==> Running migrations..."
python manage.py migrate --noinput
echo "==> Starting gunicorn..."
exec gunicorn config.wsgi:application \
--bind 0.0.0.0:8000 \
--workers 2 \
--timeout 120 \
--access-logfile - \
--error-logfile -
The entrypoint script does three things:
- Waits for the database to accept connections (retries 10 times with 3-second delays)
- Runs migrations to create or update database tables
- Starts the application server
Always wait for the database
The database may take a few seconds to become reachable after your container starts. Without a retry loop, your app will crash on first deploy. The retry pattern shown above is the recommended approach.
Django settings for AppMecca¶
Configure your Django settings.py to read database credentials from environment variables:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": os.environ.get("DB_HOST", "localhost"),
"PORT": os.environ.get("DB_PORT", "5432"),
"NAME": os.environ.get("DB_NAME", "app"),
"USER": os.environ.get("DB_USER", "app"),
"PASSWORD": os.environ.get("DB_PASSWORD", ""),
}
}
Deploy it¶
mecca register git@gitlab.com:appmecca-sample-apps/django-vue-notes.git -e dev
mecca build trigger -e dev -g git@gitlab.com:appmecca-sample-apps/django-vue-notes.git
mecca app deploy -e dev django-vue-notes
On first deploy, the entrypoint script will run python manage.py migrate and create all your database tables.
Managing your database¶
Once deployed, use the app-level backup commands. A backup is a bundle — it captures the database and any declared drives together, with a manifest, so restoring gives you a consistent set rather than a lone SQL file:
# Create a backup
mecca app backup django-vue-notes -d "initial deploy" -e dev
# List backups (newest first) — this is where you get the bundle prefix
mecca app backups django-vue-notes -e dev
# Restore from a bundle (careful -- replaces all data)
mecca app restore django-vue-notes backups/dev/django-vue-notes/main/manual/2026-01-15T10-23-45Z/ -e dev
mecca app restore takes a bundle prefix (a directory), not a path to a .sql.gz file.
Copy the prefix from the mecca app backups output.
Bringing an existing database with you
To load a pg_dump you produced elsewhere, use the dashboard's Database tab for django-vue-notes
--file dump.dump -e dev. A custom-format dump (pg_dump -Fc) is strongly preferred. This
is destructive and asks for your password to confirm.
There are no mecca db … commands — the database lifecycle is implicit. It is provisioned on
first deploy and dropped when you run mecca app delete.
See Database Operations for the full command reference.
fastapi-todos (FastAPI + SQLAlchemy)¶
An alternative approach using FastAPI and SQLAlchemy. The appmecca.yaml is nearly identical:
containers:
- name: fastapi-todos
dockerfile: Dockerfile
context: .
deploy:
- name: api
container: fastapi-todos
port: 8000
health_check:
path: /health
database:
enabled: true
The main difference is how the application handles migrations. FastAPI apps typically use Alembic:
The Entrypoint Pattern¶
Whether you use Django, FastAPI, Flask, or any other framework, the entrypoint pattern is the same:
1. Wait for database to be reachable (retry loop)
2. Run migrations (django migrate, alembic upgrade, etc.)
3. Start the application (gunicorn, uvicorn, etc.)
Use exec for the final command
Always use exec before your final command (e.g., exec gunicorn ...). This replaces the shell process with your application, so signals like SIGTERM reach your app directly. Without exec, Docker cannot gracefully stop your container.
Key Takeaways¶
- Add
database: enabled: trueto yourappmecca.yaml-- that is all the AppMecca-specific configuration needed - AppMecca injects
DATABASE_URLplusDB_HOST,DB_PORT,DB_NAME,DB_USER, andDB_PASSWORDautomatically - Read those values from the environment — the database and user names are generated, never
{app}_db - Use an entrypoint script that waits for the database, runs migrations, then starts your app
- Back up your app before deploying changes that modify the schema