Running a Django and PostgreSQL development environment in GitHub Codespaces

Helping people setup development environments (and fix them when they break) can be incredibly frustrating. I'm really excited about cloud-based development environments such as GitHub Codespaces for exactly this reason - I love the idea that you can get a working environment by clicking a green button, and if it breaks you can throw it away and click the button again to get a brand new one.

Today I figured out how to run a full Django + PostgreSQL development environment in GitHub Codespaces, configured so anyone else who creates a Codespace from that repository will get a working environment.

The .devcontainer directory

The key to this is three files in the .devcontainer directory of the repository:

You can see them in my project's .devcontainer directory here.

Here's what those files look like:

devcontainer.json

This is the most complex file. Here's what I ended up with for a classic Django app:

{
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "waitFor": "onCreateCommand",
  "updateContentCommand": "",
  "postCreateCommand": "pip install --user -r requirements.txt && python manage.py collectstatic && python manage.py migrate",
  "postAttachCommand": {
    "server": "python manage.py runserver"
  },
  "containerEnv": {
    "DATABASE_URL": "postgres://postgres:postgres@db/pillarpointstewards"
  },
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python"
      ]
    }
  },
  "portsAttributes": {
    "8000": {
      "label": "Application"
    }
  },
  "forwardPorts": [8000]
}

This references the docker-compose.yml file that we will define next, and specifies that the main service is the thing in that file that's defined in the app block.

I originally had "onAutoForward": "openPreview" in the "portsAttributes": {"8000"...} section - but this caused Codespaces to constantly attempt to open a preview window every time the Django server restarted (which was every time I typed code into a file and it auto-saved it) which was really annoying, especially since that "Simple Browser" window just showed me a Firefox error due to not wanting to embed my page in that iframe).

I haven't completely figured out the different commands, but the above recipe worked for me - where the postCreateCommand installs dependencies and runs migrations, and the postAttachCommand starts a development server.

Here's that postCreateCommand in full:

pip install --user -r requirements.txt \
  && python manage.py collectstatic \
  && python manage.py migrate

I'm installing requirements, running collectstatic (needed for my project that uses whitenoise) and then running migrations.

The containerEnv key holds environment variables. I'm telling it where to find PostgreSQL - my settings.py file then uses dj_database_url like this:

import dj_database_url

DATABASES = {}
DATABASES["default"] = dj_database_url.config()

The customizations key says that I want the VS Code Python extension.

The portsAttributes and forwardPorts pieces ensure port 8000 will be forwarded and made available for a browser tab inside the Codespaces environment.

docker-compose.yml

This file exists to ensure the PostgreSQL server is running:

version: '3.8'

services:
  app:
    build:
      context: ..
      dockerfile: .devcontainer/Dockerfile

    volumes:
      - ../..:/workspaces:cached

    command: sleep infinity
    network_mode: service:db

  db:
    image: postgres:latest
    restart: unless-stopped
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: pillarpointstewards
      POSTGRES_PASSWORD: postgres

volumes:
  postgres-data:

I figured this out by copying examples from various GitHub searches. It seems to work OK.

Note that setting POSTGRES_DB to pillarpointstewards caused a database of that name to be created when the server started.

Dockerfile

This one is really simple:

FROM mcr.microsoft.com/devcontainers/python:1-3.11-bullseye

ENV PYTHONUNBUFFERED 1

I can't remember where I found this example. It also included this comment:

# [Optional] If your requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
#    && rm -rf /tmp/pip-tmp

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>

Running Codespaces

With this .devcontainer/ directory setup for a repository, starting a Codespace for it can be done from the green Code button menu:

That menu opens an area with a big green Create codespace on main button

Clicking that button will churn away for about a minute building and launching the container, then drop into a VS Code like environment running in your browser where it will churn away for a few more minutes running the various setup commands.

Setting up your codespace window, showing the output of build logs

Once it's finished starting up, there should be a Django development server running. To view that in your browser, click on the "Ports" tab and then hover over the development server and click the little globe icon:

The ports tab shows available ports, with their local addresses

This should open a new browser window that exposes the port-forwarded development server.

ALLOWED_HOSTS

GitHub Codespaces assigns a random subdomain to your development environment every time you start it.

I found I needed to add this to settings.py:

ALLOWED_HOSTS = ["*"]

Created 2023-08-10T16:17:49-07:00, updated 2023-08-16T12:23:26-07:00 · History · Edit