Skip to content

Part 2: Adding a Database

Connect your Django app to a managed PostgreSQL database, create models, and build a REST API.

What you'll learn

  • Enabling a managed PostgreSQL database for your application
  • Creating Django models and DRF serializers for a Todo API
  • Writing an entrypoint script that waits for the database and runs migrations
  • Creating, backing up, restoring, and downloading database backups

What you'll need


Step 1: Understand how the database appears

There is no create command. Databases are provisioned implicitly: set database: enabled: true in appmecca.yaml and deploy. On the first deploy AppMecca creates the database and its user on the managed PostgreSQL server, stores the generated password in the platform secret store, and injects the connection details into every container of the app. Re-deploying is idempotent.

Database and user names are opaque

AppMecca derives the database and user names as a deterministic hash — they look like am_9f3a1c4e88b2_db and am_9f3a1c4e88b2_user, never todo_app_db. Never hard-code them. Read DATABASE_URL (or the individual DB_* variables) from the environment.

The names are also derived per branch, so main and a feature branch each get their own separate database. Data does not cross branches.

There are no mecca db commands

The database lifecycle is entirely implicit: created on first deploy, dropped by mecca app delete. Backups and restores go through mecca app backup / mecca app restore, covered in Step 10; uploading a pg_dump you produced elsewhere is the dashboard's Database tab.

Step 2: Update dependencies

Edit requirements.txt:

django==5.1
djangorestframework==3.15
gunicorn==23.0
psycopg2-binary==2.9

Step 3: Add the Todo model

Edit core/models.py:

from django.db import models


class Todo(models.Model):
    title = models.CharField(max_length=255)
    done = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

Step 4: Add DRF serializers and views

Create core/serializers.py:

from rest_framework import serializers
from .models import Todo


class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ["id", "title", "done", "created_at"]
        read_only_fields = ["id", "created_at"]

Replace core/views.py:

from django.http import JsonResponse
from rest_framework import viewsets
from .models import Todo
from .serializers import TodoSerializer


def health(request):
    return JsonResponse({"status": "healthy"})


class TodoViewSet(viewsets.ModelViewSet):
    queryset = Todo.objects.all()
    serializer_class = TodoSerializer

Update config/urls.py:

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from core.views import health, TodoViewSet

router = DefaultRouter()
router.register(r"todos", TodoViewSet)

urlpatterns = [
    path("health", health),
    path("api/", include(router.urls)),
]

Add "rest_framework" to INSTALLED_APPS in config/settings.py.

Step 5: Configure database settings

Update config/settings.py to read the connection string from the environment. Parsing DATABASE_URL is the safest route because it works unchanged no matter what the platform names the database:

import os
from urllib.parse import urlparse

_url = urlparse(os.environ["DATABASE_URL"])

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": _url.path.lstrip("/"),
        "USER": _url.username,
        "PASSWORD": _url.password,
        "HOST": _url.hostname,
        "PORT": str(_url.port or 5432),
    }
}

Automatic credentials

When database: enabled: true is set in appmecca.yaml, AppMecca injects DATABASE_URL plus the individual DB_HOST, DB_PORT, DB_NAME, DB_USER and DB_PASSWORD variables into every container of the app. Use whichever shape your framework prefers — but never assume the values: DB_NAME and DB_USER are the opaque hashed names described in Step 1.

Step 6: Write the entrypoint script

Create entrypoint.sh at the repository root. It waits for the database, runs migrations, then starts gunicorn:

#!/bin/bash
set -e
echo "Waiting for database..."
while ! python -c "import socket; s=socket.socket(); s.connect(('${DB_HOST}',int('${DB_PORT}'))); s.close()" 2>/dev/null; do sleep 1; done
echo "Running migrations..."
python manage.py migrate --noinput
echo "Starting server..."
exec gunicorn config.wsgi:application --bind 0.0.0.0:8000

Make it executable: chmod +x entrypoint.sh.

Step 7: Update the Dockerfile

FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
RUN python manage.py collectstatic --noinput 2>/dev/null || true
RUN chmod +x entrypoint.sh

EXPOSE 8000
CMD ["./entrypoint.sh"]

Step 8: Update appmecca.yaml

database:
  enabled: true

containers:
  - name: todo-app
    dockerfile: Dockerfile
    context: .

deploy:
  - name: web
    container: todo-app
    port: 8000
    health_check:
      path: /health

Declare your connection pool size

database.max_connections (default 5, range 1-50) tells AppMecca how many connections each replica of this app may open. The platform sums that across every database-enabled app and refuses deploys that would exhaust the shared PostgreSQL server. Django's default is one connection per worker process, so a 4-worker gunicorn wants:

database:
  enabled: true
  max_connections: 5

Commit and push, then build and deploy:

mecca build trigger -e dev -g git@github.com:yourorg/todo-app.git
mecca app deploy todo-app -e dev

Step 9: Test the API

Once deployed, test the endpoints against your app URL (shown by mecca app show). The hostname always includes the service name:

APP_URL=https://todo-app-web-yourgroup.dev.yourdomain.com

# Create a todo
curl -X POST "$APP_URL/api/todos/" \
  -H "Content-Type: application/json" -d '{"title": "Learn AppMecca"}'

# List, update, delete
curl "$APP_URL/api/todos/"
curl -X PUT "$APP_URL/api/todos/1/" \
  -H "Content-Type: application/json" -d '{"title": "Learn AppMecca", "done": true}'
curl -X DELETE "$APP_URL/api/todos/1/"

Step 10: Manage database backups

A backup bundle captures the database and every declared drive in one manifest-verified archive. Create one before making further changes:

mecca app backup todo-app -d "initial schema" -e dev

List backups (newest first) and note the bundle prefix of the one you want:

mecca app backups todo-app -e dev
mecca app backups todo-app -e dev --json
mecca app backups todo-app -e dev -n 50     # show more than the default 20

Download a bundle locally — this prints time-limited URLs for each object in it:

mecca app download todo-app BUNDLE_PREFIX -e dev

Restore from a bundle:

mecca app restore todo-app BUNDLE_PREFIX -e dev --force
mecca app restore todo-app BUNDLE_PREFIX -e dev --db-only   # leave drives alone

Restore is destructive

Restoring replaces the current database contents entirely, and prompts for confirmation unless --force is passed. Always create a fresh backup before restoring.

Importing an existing database

To bring an existing PostgreSQL database onto AppMecca, upload a pg_dump file directly:

# Dashboard -> todo-app -> Database tab -> choose the file -> confirm
mecca queue status ACTION_ID -e dev   # follow the queue row

A custom-format dump (pg_dump -Fc) is recommended. This is destructive and prompts for your password.


What's next

Your backend is complete. In Part 3: Going Full-Stack, you will add a React frontend and deploy both containers from a single repository.