Skip to content

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:

  1. Provisions a managed PostgreSQL database for the app, on its first deploy
  2. Injects connection credentials as environment variables into your containers
  3. 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:

  1. Waits for the database to accept connections (retries 10 times with 3-second delays)
  2. Runs migrations to create or update database tables
  3. 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:

# In your entrypoint.sh
alembic upgrade head
uvicorn main:app --host 0.0.0.0 --port 8000

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: true to your appmecca.yaml -- that is all the AppMecca-specific configuration needed
  • AppMecca injects DATABASE_URL plus DB_HOST, DB_PORT, DB_NAME, DB_USER, and DB_PASSWORD automatically
  • 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