Unified Django development toolkit for your team using VSCode - Part 1

Unified Django development toolkit for your team using VSCode - Part 1

How to setup a Django project with VSCode dev containers and docker compose

docker compose up -d is amazing right?

You don't know what is does? In simple words it will create required services to make a specific project up and running.

I will not cover basic knowledge about docker and docker compose, there are plenty of tutorials/videos doing deep introduction for this.

Objectif

We will have a VSCode + Docker compose setup which will run the project in VSCode dev containers, this will make sure that all services are up and running (DB, Django's runserver, Cache ...) We will also setup useful VSCode extensions which will boost productivity and will be available for anyone using the project with VSCode.

Requirements

// Mac M1/M2 users please use Apple Silicon version for Docker Desktop and VSCode for a better performance

VSCode dev container (Developing inside a Container)

Why?

  • How many time did you have to install virtualenv in your machine to enable full features of the Django project you're working on?
  • Wasn't you annoyed of how you will keep them up to date if your team use Docker or/and Docker compose and sync them.
  • What about installing system dependencies needed for compilation for some scenarios (python dev, imaging libraries, librdkafka, libmemcached-dev, postgresql headers if not using psycopg2-binary etc)?
  • And how much time did you already spend on this and you will spend on each new install of your OS or some new upgrades?

What about the time spent by your team or team members on that and specially when using different OS (Mac, Linux, Windows)?

What about opening VSCode (No terminal is needed) and then everything is setup for you out of the box, you will only need git, docker/docker desktop and that's all !

This will also keep your machine clean from different dependencies, you don't have to switch between dependencies version or system packages version, Dockerize everything!

For a full documentation on setting up dev containers please check this official link. I will later on refer to some section to it.

Dummy Django project

We will start by cloning a dummy Django project which don't do much and try to add Dockerfile, docker-compose.yml and dev container setup to it.

git clone --branch initial_project_setup https://github.com/MounirMesselmeni/django-vscode-dev-containers.git

This a simple Django project containing the required setup with Dockerfile and docker-compose.yml, we use postgres image for database

Project structure

image.png

Dockerfile

FROM python:3.10

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV LC_ALL=C.UTF-8

WORKDIR /app

COPY requirements.txt /app/

RUN pip install -r requirements.txt

docker-compose.yml

version: "3.9"

services:
  django:
    build:
      context: .
      dockerfile: ./Dockerfile
    command: python manage.py runserver 0.0.0.0:8000
    ports:
      - 8000:8000
    volumes:
      - .:/app:cached
    depends_on:
      - postgres
  postgres:
    image: postgres:15.0-alpine
    user: postgres
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=app
      - POSTGRES_DB=app
    volumes:
      # with named volume we will persist database state E.g after restarting docker/machine
      - postgres-data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  postgres-data:

Add a .env file under django_vscode_dev_containers folder

DEBUG=yes
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY=django-insecure-qk0&tbbj(&c9x^t6wu6wkwwg@xox^t0*@(dbo3et03x*+1-avy
ALLOWED_HOSTS=localhost,127.0.0.1,::1,0.0.0.0
DATABASE_URL=psql://app:app@postgres:5432/app

Setting up dev containers

We can do that using VSCode command palette and dev containers extension helper but for the sake of simplicity we will do this by hand.

Create a .devcontainer/devcontainer.json file in the main directory

// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/docker-existing-docker-compose
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
    "name": "Django VSCode Dev Containers",

    // Update the 'dockerComposeFile' list if you have more compose files or use different names.
    // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
    "dockerComposeFile": [
        "../docker-compose.yml",
        "docker-compose.yml"
    ],

    // The 'service' property is the name of the service for the container that VS Code should
    // use. Update this value and .devcontainer/docker-compose.yml to the real service name.
    "service": "django",

    // The optional 'workspaceFolder' property is the path VS Code should open by default when
    // connected. This is typically a file mount in .devcontainer/docker-compose.yml
    "workspaceFolder": "/app",

    "runServices": [
        "django",
        "postgres"
    ]

    // Uncomment the next line if you want to keep your containers running after VS Code shuts down.
    // "shutdownAction": "none",

    // Uncomment the next line to run commands after the container is created - for example installing curl.
    // "postCreateCommand": "apt-get update && apt-get install -y curl",

    // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
    // "remoteUser": "vscode"
}

Note on runServices, we define here which services we want VSCode to make sure they are up and running, we could have a redis service which we don't need, or it's essential to the proper functioning of the application so we would like VSCode to take care of running it with docker/docker-compose.

And devcontainers/docker-compose.yml

version: '3.9'
services:
  # Update this to the name of the service you want to work with in your docker-compose.yml file
  django:
    # If you want add a non-root user to your Dockerfile, you can use the "remoteUser"
    # property in devcontainer.json to cause VS Code its sub-processes (terminals, tasks, 
    # debugging) to execute as the user. Uncomment the next line if you want the entire 
    # container to run as this user instead. Note that, on Linux, you may need to 
    # ensure the UID and GID of the container user you create matches your local user. 
    # See https://aka.ms/vscode-remote/containers/non-root for details.
    #
    # user: vscode

    # Overrides default command so things don't shut down after the process ends.
    command: /bin/sh -c "while sleep 1000; do :; done"

Open the Command Palette (Ctrl/Cmd+shift+p) and search for Rebuild and reopen in Container

image.png

Now VSCode will open the project inside the container, we can see the docker building command and output by clicking on Starting Dev container show logs

image.png

The logs would look like this image.png

We know that we are developing inside a container in the bottom left corner we can see the name we provided in devcontainer.json file image.png

Setting up python extensions

Now we need more productivity by setting up proper python extensions to use code completion etc with Pylance.

Let's add required extensions list into the .devcontainers/devcontainer.json and some custom vscode setting (Yes you can define VSCode settings in dev container and share them with others)

  "settings": {
        "python.terminal.activateEnvironment": false,
        "python.terminal.activateEnvInCurrentTerminal": true,
        "python.pythonPath": "/usr/local/bin/python",
        "python.defaultInterpreterPath": "/usr/local/bin/python",
        "python.languageServer": "Pylance",
        // file formatting options
        "files.trimTrailingWhitespace": true,
        "files.insertFinalNewline": true,
        // files to exclude from all checks
        "files.exclude": {
            "**/*.pyc": true,
            "**/.git": false,
            "**/migrations/*": false
        },
        "python.analysis.extraPaths": [
            "/usr/local/lib/python3.10/site-packages/"
        ],
        "python.analysis.useImportHeuristic": true,
        "python.analysis.autoSearchPaths": true
    },
   "extensions": [
        "ms-python.python",
        "ms-python.vscode-pylance",
        "aaron-bond.better-comments",
        "visualstudioexptteam.vscodeintellicode",
        "mikestead.dotenv"
    ]

Command Palette and search for Rebuild to rebuilt the container and restart:

image.png

Now we can see VSCode is installing the extensions inside the dev container

image.png

A common problem to this, that sometime Pylance does not work properly from the first time and you need to restart or rebuild, and everytime you add a requirement or change dockerfile VSCode will re-install all the extensions. To fix this we can avoid VSCode extensions reinstalls by adding a persisted volume for those extensions.

Let's modify our Dockerfile to create required extensions folder

FROM python:3.10

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV LC_ALL=C.UTF-8

# Yes we are using the root user now, it's not encouraged but this is only for demo purposes
RUN mkdir -p /root/.vscode-server/extensions /root/.vscode-server-insiders/extensions

WORKDIR /app

COPY requirements.txt /app/

RUN pip install -r requirements.txt

And add the new docker volume in the .devcontainer/docker-compose.yml

version: '3.9'
services:
  # Update this to the name of the service you want to work with in your docker-compose.yml file
  django:
    # If you want add a non-root user to your Dockerfile, you can use the "remoteUser"
    # property in devcontainer.json to cause VS Code its sub-processes (terminals, tasks, 
    # debugging) to execute as the user. Uncomment the next line if you want the entire 
    # container to run as this user instead. Note that, on Linux, you may need to 
    # ensure the UID and GID of the container user you create matches your local user. 
    # See https://aka.ms/vscode-remote/containers/non-root for details.
    #
    # user: vscode

    # Overrides default command so things don't shut down after the process ends.
    command: /bin/sh -c "while sleep 1000; do :; done"
    volumes:
      - vscode_extensions:/root/.vscode-server/extensions

volumes:
  vscode_extensions:

Now rebuild again using commands palette

image.png

We can re-run Rebuild without Cache just to test out if the extensions changes are working properly image.png

Now we don't re-install extensions as they are persisted in the docker volume

image.png

We should be able now to have autocompletion working and navigating/going to third party packages like environ in settings.py with Cmd/Ctrl click

image.png

image.png

To go inside the container (Our case the django/python one) you click in the + button as shown bellow

image.png

image.png

We can run our runserver from there

./manage.py runserver 0:8000

And access localhost:8000

image.png

End

Nice what do you think?

This might looks like a lot of work, but you will set it up once and after it's mostly copy paste and you have your environment easy to work with your colleagues/teammates and simplify the process.

Final result could be find in the github repo:

Part 2 (Next)

  • I will add a part 2 soon in which we will cover how to use git from inside the container, this is handy if you want to have some pre-commit scripts and you have no interest to install that on the host machine or if you like using the git integration inside VSCode.
  • Setup automatic formatting using black - On save
  • Setup automatic imports sorting using isort - On save
  • Pylint/Flake8 previews inside VSCode