<!--
  canonical-url: https://www.avonture.be/fr/blog/docker-python-devcontainer
  published:     2024-10-30
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Docker - Python devcontainer

> Découvrez comment mettre en place rapidement un devcontainer VSCode pour développer en Python avec Docker. Récupérez le Dockerfile complet, le compose.yaml et les configurations .docker.env.

---
![Docker - Python devcontainer](/img/v2/devcontainer.webp)

**TL;DR**

Cet article construit un devcontainer Python réutilisable pour VSCode : un `Dockerfile`, un `compose.yaml`, un `.docker.env` (pour les réglages propres à chaque projet, comme la version de Python) et un `makefile` qui enveloppe les commandes courantes (`make up`, `make bash`), puis un `.devcontainer/devcontainer.json` préconfiguré avec l'extension Python et les settings nécessaires. Résultat : `make devcontainer` ouvre VSCode qui tourne entièrement dans le container, avec les fichiers synchronisés avec l'host.

Comme vous le savez, VSCode est un superbe éditeur qui vous permet de programmer dans probablement n'importe quel langage. Un éditeur, pas un IDE, parce que VSCode est au fond un Notepad dans sa version ultime.

Si vous voulez programmer en Python, vous aurez besoin d'installer quelques extensions dans VSCode pour être vraiment à l'aise : coloration syntaxique, navigation dans le code, refactoring (renommer une variable ou une classe), etc.

Il existe des éditeurs « prêts à l'emploi » comme PyCharm mais 1. ils sont payants et 2. ils sont spécifiques (vous ne pourrez pas programmer en PHP avec PyCharm ; ni même travailler facilement avec des fichiers HTML/CSS).

Dans ce nouvel article, nous allons voir comment obtenir un environnement VSCode immédiatement prêt à l'emploi pour coder en Python. Et comme c'est VSCode, c'est 1. gratuit, 2. polyvalent et 3. terriblement puissant.

## Voyons-le tourner {#lets-see-it-running}

Voici ce que vous obtenez, avant même d'avoir créé un seul fichier : lancez `make up` dans votre console et vous verrez cet écran :

![Makefile](./images/make.webp)

Comme vous le voyez, nous avons tout un tas de commandes, comme `make up` pour démarrer notre container Docker. Essayons, et aïe, il nous manque un fichier appelé `/src/requirements.txt`.

![requirements.txt is missing](./images/requirements_txt.webp)

### src/requirements.txt {#srcrequirementstxt}

Créez un fichier vide appelé `src/requirements.txt`. Le fichier peut rester vide (vous y ajouterez vos propres dépendances plus tard).

En relançant `make up`, ça y est ! Cette fois nous pouvons construire nos images et créer notre container :

![Docker up](./images/docker-up.webp)

Cette fois, si besoin, nous pouvons entrer dans notre container en lançant `make bash` et, par exemple, vérifier ce que contient le dossier courant et quelle version de Python a été installée :

![Inside the container](./images/container-python.webp)

> **Note:**
>
> Comme vous le voyez, c'est Python 3.13 qui est utilisé. Pourquoi cette version précise ? Retournez dans votre fichier `.docker.env` et regardez la variable `DOCKER_PYTHON_VERSION`. Si vous en voulez une autre, mettez simplement à jour le fichier `.docker.env` et relancez `make up`.

## Créons notre premier script {#lets-start-creating-our-first-script}

Si vous êtes toujours dans votre container, tapez `exit` afin que votre prompt soit celui de votre machine (votre *host*), et non plus celui du container en cours d'exécution.

Créez votre premier script : un nouveau fichier dans le dossier `src`, appelé `hello.py`.

Soyez créatif et tapez un simple `print` :

```python
print("I'm your Python code")
```

![Hello](./images/hello.webp)

Pour pouvoir exécuter le code, relancez `make bash` (pour entrer dans le container) puis lancez `python hello.py`

![Run hello](./images/run-hello.webp)

> **Information:**
>
> Ça fonctionne parce que, dans le container, le répertoire de travail est `/app/src`. Si ça n'avait pas été le cas, il aurait fallu écrire par exemple `python /app/src/hello.py`, c'est-à-dire le chemin absolu vers le script.

Comme vous pouvez le constater, les fichiers de votre machine sont synchronisés avec votre host. Si VSCode est toujours ouvert, vous pouvez modifier votre script, par exemple :

```python
print("Hey! It's synchronized; cool!")
```

![It's synchronized](./images/it-is-synchronized.webp)

Les fichiers derrière tout ça — `Dockerfile`, `compose.yaml`, `.docker.env` et le `makefile` — sont présentés juste après.

## Créons les fichiers de notre environnement Docker {#lets-create-the-files-for-our-docker-environment}

Dans cet article, nous allons créer le devcontainer, c'est-à-dire l'environnement de développement basé sur Docker que vous venez de voir tourner. La création du devcontainer prendra quelques minutes, mais il s'agit juste de copier/coller depuis cet article vers votre système.

Une fois les fichiers créés, vous pourrez réutiliser le devcontainer pour tous vos projets Python.

*Deux prolongements naturels : ajouter les outils de [Python - Code Quality tools](/blog/python-qa) à l'image pour que chaque projet en hérite, et lire [One Docker Image for Production and Devcontainers - The Clean Way](/blog/docker-prod-devcontainer) pour garder cet outillage en dehors de l'image que vous déployez.*

Comme toujours, nous allons créer un nouveau dossier et y créer des fichiers. Lancez `mkdir /tmp/python && cd $_` pour créer ce dossier et vous y placer.

Démarrez VSCode depuis là, c'est-à-dire lancez `code .` dans la console quand vous êtes dans le dossier. Cela ouvrira VSCode et vous pourrez créer autant de fichiers que vous voulez.

### Dockerfile {#dockerfile}

Le premier fichier à créer servira à construire notre image Docker. Créez un fichier appelé `Dockerfile` avec le contenu suivant :

**Dockerfile**

```docker
# syntax=docker/dockerfile:1

# cspell:ignore PYTHONDONTWRITEBYTECODE,PYTHONUNBUFFERED,HISTFILE
# cspell:ignore addgroup,adduser,keyscan,hadolint,gecos,endregion

# Those variables are initialized in the .docker.env file
ARG DOCKER_APP_HOME="/app"
ARG DOCKER_CONTAINER_NAME="python"
ARG DOCKER_OS_GROUPID=1000
ARG DOCKER_OS_USERID=1000
ARG DOCKER_OS_USERNAME="python"
ARG DOCKER_PYTHON_VERSION=3.10-slim

# region - Our Python base image. We'll install Linux and Python dependencies here
# and do some other configuration work

FROM python:${DOCKER_PYTHON_VERSION} AS base

# Prevents Python from writing pyc files.
ENV PYTHONDONTWRITEBYTECODE=1

# Keeps Python from buffering stdout and stderr to avoid situations where
# the application crashes without emitting any logs due to buffering.
ENV PYTHONUNBUFFERED=1

ARG DOCKER_APP_HOME
WORKDIR "${DOCKER_APP_HOME}/src"

# hadolint ignore=DL3008
RUN --mount=type=cache,target=/var/cache/apk,rw \
    set -e -x \
    && printf "\e[0;105m%s\e[0;0m\n" "Install required Linux binaries..."  \
    && apt-get update -yqq \
    && apt-get install -y --no-install-recommends bash git openssh-client tree \
    && apt-get clean \
    && rm -rf /tmp/* /var/lib/apt/lists/*

# Install Python dependencies
#
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
# Leverage a bind mount to requirements.txt to avoid having to copy them into
# into this layer.

RUN --mount=type=cache,target=/root/.cache/pip \
    --mount=type=bind,source=src/requirements.txt,target=requirements.txt \
    printf "\e[0;105m%s\e[0;0m\n" "Install Python dependencies" \
    && python -m pip install --no-cache-dir -r requirements.txt

# Keep the container running
ENTRYPOINT ["tail", "-f", "/dev/null"]

# endregion

# region - Define our development image
FROM base AS development

ENV SHELL /bin/bash

# Our user will be part of the root group since we're building the development image
ARG DOCKER_OS_GROUPID
ARG DOCKER_OS_USERID
ARG DOCKER_OS_USERNAME

RUN set -e -x \
    && mkdir -p "/home/.vscode-server/bin" \
    && mkdir -p "/home/.vscode-server/extensions" \
    && mkdir -p "/home/.vscode-server/extensionsCache" \
    && printf "\e[0;105m%s\e[0;0m\n" "Create our ${DOCKER_OS_USERNAME} application user" \
    && mkdir -p "/home/${DOCKER_OS_USERNAME}/.vscode-server/bin" \
    && mkdir -p "/home/${DOCKER_OS_USERNAME}/.vscode-server/extensions" \
    && mkdir -p "/home/${DOCKER_OS_USERNAME}/.vscode-server/extensionsCache" \
    # Create the application user home directory
    && mkdir -p "/home/${DOCKER_OS_USERNAME}" \
    # Create our application user group
    && addgroup "${DOCKER_OS_USERNAME}" --gid "${DOCKER_OS_GROUPID}" \
    # Create our application user
    && adduser \
    --system \
    --disabled-password \
    --gecos "" \
    --home "/home/${DOCKER_OS_USERNAME}" \
    --uid "${DOCKER_OS_USERID}" \
    "${DOCKER_OS_USERNAME}" \
    # And, finally, set the correct permissions to the home folder of our user
    && chown -R "${DOCKER_OS_USERNAME}:${DOCKER_OS_USERNAME}" "/home/${DOCKER_OS_USERNAME}"

USER "${DOCKER_OS_USERNAME}"

ARG DOCKER_CONTAINER_NAME

RUN /bin/bash -c "echo \"PS1='\n\e[0;33m🐳 ${DOCKER_CONTAINER_NAME} \e[0;32mDEV\e[0m - \e[0;36m$(whoami)\e[0m \w # '\" >> /home/${DOCKER_OS_USERNAME}/.bashrc"

# Save the bash history in file /home/${OS_USERNAME}/commandhistory/.bash_history
# Like this we'll be able to map that folder using a volume in our
# compose.yaml file and then make the history persistent.
RUN set -e -x \
    && SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/home/${DOCKER_OS_USERNAME}/commandhistory/.bash_history" \
    && mkdir -p "/home/${DOCKER_OS_USERNAME}/commandhistory" \
    && touch "/home/${DOCKER_OS_USERNAME}/commandhistory/.bash_history" \
    && echo "${SNIPPET}" >> "/home/${DOCKER_OS_USERNAME}/.bashrc" \
    && echo "${SNIPPET}" >> "/home/${DOCKER_OS_USERNAME}/.bashrc"

# endregion
```

### compose.yaml {#composeyaml}

À côté du `Dockerfile`, nous allons créer notre `compose.yaml`. Créez ce fichier avec le contenu suivant :

**compose.yaml**

```yaml
name: app_python

services:
  app_python:
    build:
      context: .
      target: development
      args:
        # Version of Python to use
        - DOCKER_PYTHON_VERSION=${DOCKER_PYTHON_VERSION}
        # Application root directory in the container (PHP or NGINX) (--app-home)
        - DOCKER_APP_HOME=${DOCKER_APP_HOME:-/app}
        # Name of the container to show in the bash prompt
        - DOCKER_CONTAINER_NAME=${DOCKER_CONTAINER_NAME:-python}
        # Set OS groupid in your Docker Linux containers (1000 = root) (--os-groupid)
        - DOCKER_OS_GROUPID=${DOCKER_OS_GROUPID:-1000}
        # Set OS userid in your Docker Linux containers (1000 = root) (--os-userid)
        - DOCKER_OS_USERID=${DOCKER_OS_USERID:-1000}
        # Set OS username in your Docker Linux containers (--os-username)
        - DOCKER_OS_USERNAME=${DOCKER_OS_USERNAME:-python}
    user: ${DOCKER_OS_USERID:-1000}:${DOCKER_OS_GROUPID:-1000}
    env_file:
      - .docker.env
    container_name: app_python
    volumes:
      # Our codebase on our host
      - .:${DOCKER_APP_HOME}
      # Keep installed VSCode extensions in a volume to avoid to reinstall them
      - vscode-extensions:/home/${DOCKER_OS_USERNAME}/.vscode-server/extensions
      # Remember the bash history
      - bashhistory:/home/${DOCKER_OS_USERNAME}/commandhistory

volumes:
  # Use a Docker volume self-managed volume to store vscode's cache
  vscode-extensions:
  # Use a Docker volume self-managed volume to store bash history
  bashhistory:
```

### .docker.env {#dockerenv}

Le troisième fichier à créer s'appellera `.docker.env` ; nous y initialiserons quelques valeurs. Créez ce fichier avec le contenu ci-dessous :

**.docker.env**

```env
# Application root directory in the container (PHP or NGINX) (--app-home)
DOCKER_APP_HOME=/app

# Name of the container to show in the bash prompt
DOCKER_CONTAINER_NAME=app

# Set OS groupid in your Docker Linux containers (1000 = root) (--os-groupid)
DOCKER_OS_GROUPID=1000

# Set OS userid in your Docker Linux containers (1000 = root) (--os-userid)
DOCKER_OS_USERID=1000

# Set OS username in your Docker Linux containers (--os-username)
DOCKER_OS_USERNAME="python"

# Version of Python to use
DOCKER_PYTHON_VERSION=3.13-slim
```

> **Information:**
>
> Il vous suffit de dupliquer les autres fichiers que nous avons créés pour chacun de vos projets, et les réglages propres à votre projet se feront ici, dans le fichier `.docker.env`.

### makefile {#makefile}

Pour se simplifier la vie, nous allons regrouper un ensemble de commandes dans un fichier appelé `makefile`. Créez un fichier `makefile` avec le contenu ci-dessous :

**makefile**

```
# cspell:ignore ifdef,pydocstyle,isort,mypy
default: help

# Folder where Python binaries are installed
BIN=/usr/local/bin

# Name of our Docker container
CONTAINER=app_python

# The project type
DOCKER_PROJECT_TYPE=Python

# Name of our Docker environment file
DOCKER_ENV_FILE=.docker.env

# The environment file we'll use
ENV=--env-file ${DOCKER_ENV_FILE}

# The list of  Docker compose yaml file we'll use
YAML=COMPOSE_FILE=compose.yaml

COLOR_CYAN:=36
_CYAN := "\033[1;${COLOR_CYAN}m%s\033[0m %s\n"

# Required for `make devcontainer`: we need to convert a string like "python" to
# an hexadecimal string. (printf "python" | xxd -p will return 63325f66646d5f706870)
DOCKER_VSCODE:=$(shell printf "${CONTAINER}" | xxd -p)

.PHONY: bash
bash: ## App - Start an interactive shell inside your application container
	${YAML} docker compose ${ENV} exec -it ${CONTAINER} /bin/bash

.PHONY: build
build: ## App - Build a fresh Docker image for this project
	${YAML} docker compose ${ENV} build ${ARGS}

.PHONY: config
config: ## App - Show the docker configuration
	${YAML} docker compose ${ENV} config

.PHONY: devcontainer
devcontainer: ## App - Open the PHP container in vscode (you can f.i. directly start your debugging session)
	code --folder-uri vscode-remote://attached-container+${DOCKER_VSCODE}/app

.PHONY: help
help: ## App - Show the help with the list of commands
	@clear

    # Parse this file, search for `##` followed by a description
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n\n"} /^[a-zA-Z_-]+:.*?##/ { printf "  \033[36m%-21s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[0;33m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

	@echo ""

.PHONY: remove
remove: ## App - Stop containers and remove them (you will lose updated files if you do not have locally)
	${YAML} docker compose ${ENV} down --remove-orphans --rmi all --volumes

.PHONY: up
up: ## App - Based on the created Docker images (made with `make build`), create containers and start them
	${YAML} docker compose ${ENV} up --detach

##@ Quality assurance

.PHONY: doc-style
doc-style: ## QA - pydocstyle is a static analysis tool for checking compliance with Python docstring conventions
	@clear
	${YAML} docker compose ${ENV} exec ${CONTAINER} ${BIN}/pydocstyle /app/src

.PHONY: format
format: ## QA - Format the script using Black
	@clear
	@${YAML} docker compose ${ENV} exec ${CONTAINER} ${BIN}/black /app/src

.PHONY: lint
lint: ## QA - Lint the script using Pylint - Run  analyses your code without actually running it
	@clear
	${YAML} docker compose ${ENV} exec ${CONTAINER} ${BIN}/pylint /app/src

.PHONY: mypy
mypy: ## QA - Mypy is a program that will type check your Python code
	@clear
	${YAML} docker compose ${ENV} exec ${CONTAINER} ${BIN}/mypy --cache-dir /tmp/mypy
```

> **Caution:**
>
> Si vous ne savez pas si `GNU make` est déjà installé, lancez `which make` dans la console. Si vous voyez `make not found`, lancez `sudo apt-get update && sudo apt-get install make` pour procéder à l'installation.

## Qu'avons-nous fait jusqu'ici ? {#what-have-we-done-so-far}

Nous avons créé la structure minimale pour nos futurs projets Python. Nous avons défini notre image Docker (grâce au fichier `Dockerfile`) et la façon de l'utiliser (fichier `compose.yaml`).

Nous avons aussi configuré notre projet (fichier `.docker.env`) et mis en place un certain nombre de commandes utiles (fichier `makefile`).

Tous ces fichiers sont réutilisables dans l'ensemble de vos projets Python.

## Maintenant, créons notre environnement devcontainer {#now-lets-create-our-devcontainer-environment}

Il nous faut créer un fichier supplémentaire. Créez un nouveau dossier appelé `.devcontainer` et, dedans, un fichier appelé `devcontainer.json`. Copiez/collez le contenu ci-dessous :

**.devcontainer/devcontainer.json**

```json
{
    "name": "app_python",
    "dockerComposeFile": [
        "./../compose.yaml",
    ],
    "service": "app_python",
    "remoteUser": "python",
    "workspaceFolder": "/app",
    "customizations": {
        "vscode": {
            "extensions": [
                "aaron-bond.better-comments",
                "DavidAnson.vscode-markdownlint",
                "eamodio.gitlens",
                "EricSia.pythonsnippets3",
                "foxundermoon.shell-format",
                "gruntfuggly.todo-tree",
                "mde.select-highlight-minimap",
                "mgesbert.python-path",
                "mikestead.dotenv",
                "mrmlnc.vscode-duplicate",
                "ms-azuretools.vscode-docker",
                "ms-python.black-formatter",
                "ms-python.debugpy",
                "ms-python.isort",
                "ms-python.mypy-type-checker",
                "ms-python.pylint",
                "ms-python.python",
                "ms-python.vscode-pylance",
                "ms-vscode-remote.remote-containers",
                "ms-vscode.makefile-tools",
                "njpwerner.autodocstring",
                "redhat.vscode-xml",
                "redhat.vscode-yaml",
                "sirtori.indenticator",
                "sonarsource.sonarlint-vscode",
                "streetsidesoftware.code-spell-checker-dutch",
                "streetsidesoftware.code-spell-checker-french",
                "streetsidesoftware.code-spell-checker",
                "tomoki1207.pdf",
                "tyriar.sort-lines"
            ],
            "settings": {
                "[dockerfile]": {
                    "files.eol": "\n",
                    "editor.defaultFormatter": "ms-azuretools.vscode-docker"
                },
                "[json]": {
                    "editor.defaultFormatter": "vscode.json-language-features"
                },
                "[jsonc]": {
                    "editor.defaultFormatter": "vscode.json-language-features",
                    "editor.wordWrap": "wordWrapColumn",
                    "editor.wordWrapColumn": 80,
                    "editor.wrappingIndent": "indent"
                },
                "[markdown]": {
                    "editor.defaultFormatter": "DavidAnson.vscode-markdownlint",
                    "editor.wordWrap": "wordWrapColumn",
                    "editor.wordWrapColumn": 80,
                    "editor.wrappingIndent": "indent"
                },
                "[python]": {
                    "editor.defaultFormatter": "ms-python.black-formatter",
                    "editor.formatOnSave": true,
                    "editor.codeActionsOnSave": {
                        "source.fixAll": "always",
                        "source.organizeImports": "always"
                    }
                },
                "[shellscript]": {
                    "editor.defaultFormatter": "foxundermoon.shell-format"
                },
                "[xml]": {
                    "editor.defaultFormatter": "redhat.vscode-xml",
                    "editor.wordWrap": "wordWrapColumn",
                    "editor.wordWrapColumn": 80,
                    "editor.wrappingIndent": "indent"
                },
                "[yaml]": {
                    "editor.defaultFormatter": "redhat.vscode-yaml",
                    "editor.insertSpaces": true,
                    "editor.tabSize": 2
                },
                "cSpell.language": "en,fr,nl",
                "docker-explorer.enableTelemetry": false,
                "editor.bracketPairColorization.enabled": true,
                "editor.codeActionsOnSave": {
                    "source.fixAll": "explicit"
                },
                "editor.defaultFoldingRangeProvider": null,
                "editor.detectIndentation": false,
                "editor.folding": true,
                "editor.foldingStrategy": "auto",
                "editor.formatOnSave": true,
                "editor.guides.bracketPairs": "active",
                "editor.guides.bracketPairsHorizontal": "active",
                "editor.guides.highlightActiveIndentation": true,
                "editor.guides.indentation": true,
                "editor.multiCursorModifier": "ctrlCmd",
                "editor.renderWhitespace": "all",
                "editor.rulers": [
                    120,
                    70
                ],
                "editor.stickyScroll.enabled": true,
                "editor.tabCompletion": "on",
                "editor.tabSize": 4,
                "editor.wordBasedSuggestionsMode": "allDocuments",
                "editor.wordWrapColumn": 120,
                "explorer.compactFolders": false,
                "explorer.confirmDelete": true,
                "explorer.confirmDragAndDrop": true,
                "extensions.autoCheckUpdates": false,
                "files.autoSave": "onFocusChange",
                "files.defaultLanguage": "${activeEditorLanguage}",
                "files.eol": "\n",
                "files.exclude": {
                    "**/.cache": true,
                    "**/.git": true
                },
                "files.insertFinalNewline": true,
                "files.trimTrailingWhitespace": true,
                "markdown.extension.toc.levels": "2..6",
                "markdownlint.config": {
                    "MD033": false,
                    "MD036": false
                },
                "mypy-type-checker.args": [
                    "--config-file=.config/.mypy.ini"
                ],
                "python.analysis.autoFormatStrings": true,
                "python.analysis.autoImportCompletions": true,
                "python.analysis.autoIndent": true,
                "python.analysis.autoSearchPaths": true,
                "python.analysis.fixAll": [
                    "source.convertImportFormat"
                ],
                "python.analysis.typeCheckingMode": "strict",
                "python.defaultInterpreterPath": "/usr/local/bin/python3",
                "python.formatting.provider": "black",
                "python.languageServer": "Pylance",
                "python.linting.enabled": true,
                "python.linting.flake8Enabled": true,
                "python.linting.pylintEnabled": true,
                "python.sortImports.args": [
                    "--profile",
                    "black"
                ],
                "redhat.telemetry.enabled": false,
                "sonarlint.output.showAnalyzerLogs": true,
                "sonarlint.disableTelemetry": true,
                "sonarlint.rules": {
                    "docker:S7031": {
                        "level": "off"
                    }
                },
                "telemetry.telemetryLevel": "off",
                "terminal.integrated.profiles.linux": {
                    "bash": {
                        "path": "/bin/bash",
                        "icon": "terminal-bash"
                    }
                },
                "terminal.integrated.defaultProfile.linux": "bash",
                "terminal.integrated.fontFamily": "MesloLGS NF",
                "update.enableWindowsBackgroundUpdates": false,
                "update.mode": "none",
                "yaml.format.enable": true,
                "yaml.completion": true,
                "yaml.validate": true,
                "workbench.editor.enablePreview": false,
                "workbench.editor.wrapTabs": true
            }
        }
    }
}
```

Ce fichier est très long ; il pourrait être plus court, mais l'idée est de configurer VSCode. Nous avons donc listé toutes les extensions que nous voulons dans notre environnement de codage, ainsi qu'un paquet de settings.

Notre installation est terminée. Voici la structure finale de notre projet :

![Our final project's structure](./images/final-structure.webp)

## Démarrer le projet dans un devcontainer {#start-the-project-in-a-devcontainer}

Si VSCode est toujours ouvert, fermez-le. Si vous êtes toujours dans la console du container, tapez `exit` pour revenir à la console de votre host.

Maintenant, lancez `make devcontainer` (donc sur votre machine hôte), qui est l'une des commandes déjà présentes dans notre `makefile`.

VSCode démarre et ouvre le projet directement *dans* le container :

![VSCode - Devcontainer](./images/vscode_devcontainer.webp)

VSCode va demander (voir en bas à droite) d'installer l'extension Python de Microsoft ; autorisez-le et cliquez sur `Install`.

VSCode demandera aussi si vous voulez installer les extensions recommandées ; faites-le.

![Install recommended extensions](./images/vscode_install_extensions.webp)

## Conclusion {#conclusion}

Vous avez maintenant un environnement de développement Python pleinement fonctionnel. Grâce à notre image Docker, Python a été installé et configuré pour tourner dans un container Docker (comprenez : rien n'a été installé sur votre machine) et, grâce au devcontainer, vous êtes sûr que VSCode est correctement configuré avec toutes les extensions nécessaires pour travailler sereinement.

Vous êtes sous Windows et vous voulez exactement les mêmes fichiers, adaptés ? Voyez [Docker - Easy setup of Python under Windows](/blog/docker-python-devcontainer-windows). Vous préférez ne pas créer les fichiers à la main du tout ? [Docker - Even easier setup of Python](/blog/docker-python-devcontainer-microsoft) utilise l'assistant intégré de VSCode à la place.
