Skip to content

feat(template): Add VSCode devcontainers#43

Draft
CuriousLearner wants to merge 7 commits into
mainfrom
vscode-devcontainers
Draft

feat(template): Add VSCode devcontainers#43
CuriousLearner wants to merge 7 commits into
mainfrom
vscode-devcontainers

Conversation

@CuriousLearner

Copy link
Copy Markdown
Owner

Summary

Add VS Code devcontainer support to generated projects. The devcontainer reuses the existing Docker Compose setup and provides a one-click containerized development environment.

Related Issues

Fixes #42

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Template configuration change
  • CI/CD update
  • Dependency update
  • Refactoring (no functional changes)

What Changed

Two new Jinja template files added under template/.devcontainer/:

  • devcontainer.json.jinja - Main devcontainer config that references the existing
    docker-compose.yml via a compose override pattern. Includes conditional port
    forwarding (Redis, Vite, Temporal, Jaeger), minimal VS Code extensions (Python,
    Pylance, Ruff, Django, Docker, GitLens), correct Python interpreter path for
    uv/Poetry, and a post-create command to install deps and run migrations.
  • docker-compose.devcontainer.yml.jinja - Override compose file that replaces the
    web service command with sleep infinity, allowing VS Code to manage the
    container lifecycle instead of gunicorn.

Additional Notes


Reviewer Notes:

Copilot AI review requested due to automatic review settings March 8, 2026 01:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds VS Code Dev Container support to projects generated from this template, reusing the existing Docker Compose-based dev stack to provide a containerized, “open in container” workflow.

Changes:

  • Introduces a devcontainer.json template that attaches VS Code to the existing web Compose service and forwards common service ports conditionally.
  • Adds a Compose override template intended to keep the web container running under VS Code control (via sleep infinity).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
template/.devcontainer/devcontainer.json.jinja Devcontainer definition (Compose-based), port forwarding, editor extensions/settings, and post-create setup command.
template/.devcontainer/docker-compose.devcontainer.yml.jinja Compose override for devcontainer usage (overrides the web service command/environment).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +9 to +17
"forwardPorts": [
8000,
5432,
8025{% if cache == 'redis' %},
6379{% endif %}{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %},
5173{% endif %}{% if background_tasks in ['temporal', 'both'] %},
7233,
8080{% endif %}{% if observability_level == 'full' %},
16686{% endif %}

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Jinja conditionals in the forwardPorts array will render invalid JSON in cases where the feature flags are disabled. Commas placed immediately after {% if ... %} (e.g., on the frontend, background_tasks, observability_level blocks) are literal output and will remain even when the condition is false, leaving dangling commas in the array. Move commas inside the conditional blocks (or construct the list in Jinja and serialize it) so the rendered file is always valid JSON.

Suggested change
"forwardPorts": [
8000,
5432,
8025{% if cache == 'redis' %},
6379{% endif %}{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %},
5173{% endif %}{% if background_tasks in ['temporal', 'both'] %},
7233,
8080{% endif %}{% if observability_level == 'full' %},
16686{% endif %}
{% set forward_ports = [8000, 5432, 8025] %}
{% if cache == 'redis' %}
{% set _ = forward_ports.append(6379) %}
{% endif %}
{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %}
{% set _ = forward_ports.append(5173) %}
{% endif %}
{% if background_tasks in ['temporal', 'both'] %}
{% set _ = forward_ports.append(7233) %}
{% set _ = forward_ports.append(8080) %}
{% endif %}
{% if observability_level == 'full' %}
{% set _ = forward_ports.append(16686) %}
{% endif %}
"forwardPorts": [
{% for port in forward_ports %}
{{ port }}{% if not loop.last %},{% endif %}
{% endfor %}

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +27
8025{% if cache == 'redis' %},
6379{% endif %}{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %},
5173{% endif %}{% if background_tasks in ['temporal', 'both'] %},
7233,
8080{% endif %}{% if observability_level == 'full' %},
16686{% endif %}
],
"portsAttributes": {
"8000": { "label": "Django", "onAutoForward": "notify" },
"5432": { "label": "PostgreSQL", "onAutoForward": "silent" },
"8025": { "label": "Mailpit UI", "onAutoForward": "silent" }{% if cache == 'redis' %},
"6379": { "label": "Redis", "onAutoForward": "silent" }{% endif %}{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %},
"5173": { "label": "Vite", "onAutoForward": "notify" }{% endif %}{% if background_tasks in ['temporal', 'both'] %},
"7233": { "label": "Temporal", "onAutoForward": "silent" },
"8080": { "label": "Temporal UI", "onAutoForward": "silent" }{% endif %}{% if observability_level == 'full' %},
"16686": { "label": "Jaeger UI", "onAutoForward": "silent" }{% endif %}

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

portsAttributes has the same JSON rendering problem as forwardPorts: commas placed outside the {% if ... %} blocks (right after the opening {% if %} tag) will be emitted even when the condition is false, producing invalid JSON (e.g., a trailing comma after the Mailpit entry when Redis is disabled). Adjust the template so commas are only emitted when the corresponding attribute entry is emitted.

Suggested change
8025{% if cache == 'redis' %},
6379{% endif %}{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %},
5173{% endif %}{% if background_tasks in ['temporal', 'both'] %},
7233,
8080{% endif %}{% if observability_level == 'full' %},
16686{% endif %}
],
"portsAttributes": {
"8000": { "label": "Django", "onAutoForward": "notify" },
"5432": { "label": "PostgreSQL", "onAutoForward": "silent" },
"8025": { "label": "Mailpit UI", "onAutoForward": "silent" }{% if cache == 'redis' %},
"6379": { "label": "Redis", "onAutoForward": "silent" }{% endif %}{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %},
"5173": { "label": "Vite", "onAutoForward": "notify" }{% endif %}{% if background_tasks in ['temporal', 'both'] %},
"7233": { "label": "Temporal", "onAutoForward": "silent" },
"8080": { "label": "Temporal UI", "onAutoForward": "silent" }{% endif %}{% if observability_level == 'full' %},
"16686": { "label": "Jaeger UI", "onAutoForward": "silent" }{% endif %}
8025
{% if cache == 'redis' %}
, 6379
{% endif %}
{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %}
, 5173
{% endif %}
{% if background_tasks in ['temporal', 'both'] %}
, 7233,
8080
{% endif %}
{% if observability_level == 'full' %}
, 16686
{% endif %}
],
"portsAttributes": {
"8000": { "label": "Django", "onAutoForward": "notify" },
"5432": { "label": "PostgreSQL", "onAutoForward": "silent" },
"8025": { "label": "Mailpit UI", "onAutoForward": "silent" }
{% if cache == 'redis' %}
, "6379": { "label": "Redis", "onAutoForward": "silent" }
{% endif %}
{% if frontend == 'htmx-tailwind' and frontend_bundling == 'vite' %}
, "5173": { "label": "Vite", "onAutoForward": "notify" }
{% endif %}
{% if background_tasks in ['temporal', 'both'] %}
, "7233": { "label": "Temporal", "onAutoForward": "silent" },
"8080": { "label": "Temporal UI", "onAutoForward": "silent" }
{% endif %}
{% if observability_level == 'full' %}
, "16686": { "label": "Jaeger UI", "onAutoForward": "silent" }
{% endif %}

Copilot uses AI. Check for mistakes.
}
}
},
"postCreateCommand": "{% if dependency_manager == 'uv' %}uv sync && uv run python manage.py migrate{% else %}poetry install && python manage.py migrate{% endif %}"

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Poetry projects, postCreateCommand runs poetry install but the devcontainer sets python.defaultInterpreterPath to /usr/local/bin/python and then runs python manage.py migrate. By default Poetry creates a virtualenv for non-root users, so dev dependencies may be installed into a venv that VS Code (and the python invocation) won’t use. Consider forcing virtualenvs.create=false in the devcontainer (e.g., via env var or a poetry config ... --local step) or switching the migration command and interpreter selection to use the Poetry environment consistently.

Suggested change
"postCreateCommand": "{% if dependency_manager == 'uv' %}uv sync && uv run python manage.py migrate{% else %}poetry install && python manage.py migrate{% endif %}"
"postCreateCommand": "{% if dependency_manager == 'uv' %}uv sync && uv run python manage.py migrate{% else %}poetry config virtualenvs.create false --local && poetry install && python manage.py migrate{% endif %}"

Copilot uses AI. Check for mistakes.
web:
command: sleep infinity
environment:
- DEBUG=True

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Docker Compose override semantics, the environment: sequence here will replace the environment: sequence from ../docker-compose.yml rather than merge with it. That drops important container-specific env vars from the base file (e.g., DATABASE_URL pointing at db, EMAIL_HOST=mailpit, optional REDIS_URL, etc.), which can break the devcontainer unless the user hand-edits .env. Consider removing environment from the override entirely, or duplicating/retaining the base environment entries in the override so the merged config stays functional out-of-the-box.

Suggested change
- DEBUG=True
DEBUG: "True"

Copilot uses AI. Check for mistakes.
Disable Poetry virtualenv creation in postCreateCommand to ensure
consistent interpreter path, and remove redundant environment override
from compose file since Docker Compose merges environment entries.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread template/.devcontainer/devcontainer.json.jinja
Comment thread template/.devcontainer/devcontainer.json.jinja
Add initializeCommand to copy .env.example when .env is missing so
devcontainer starts without manual setup. Add tests to verify rendered
devcontainer JSON and YAML are valid across all option combinations.
Copilot AI review requested due to automatic review settings March 14, 2026 21:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +10
"workspaceFolder": "/app",
"initializeCommand": "if [ ! -f .env ] && [ -f .env.example ]; then cp .env.example .env; fi",
"forwardPorts": [
Comment on lines +47 to +49
}
},
"postCreateCommand": "{% if dependency_manager == 'uv' %}uv sync && uv run python manage.py migrate{% else %}poetry config virtualenvs.create false --local && poetry install && python manage.py migrate{% endif %}"
* main:
  chore(deps): upgrade all dev and CI dependencies (#58)
- Replace initializeCommand with onCreateCommand so .env copy runs
  inside the container (Linux shell), fixing Windows host portability
- Switch Poetry from virtualenvs.create=false --local to
  virtualenvs.in-project=true so the venv lands at /app/.venv,
  avoiding permission failures with the non-root container user and
  preventing poetry.toml from dirtying the generated project
- Unify python.defaultInterpreterPath to /app/.venv/bin/python for
  both uv and Poetry since both now use an in-project venv
- Update test to assert the new unified interpreter path
Copilot AI review requested due to automatic review settings May 2, 2026 20:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -0,0 +1,50 @@
{
"name": "{{ project_name }}",
project_name was interpolated raw into a quoted JSON string, so a name
containing double-quotes would produce invalid devcontainer.json.
Switch to the tojson filter so Jinja handles escaping correctly.
Add a regression test that asserts names with quotes round-trip through
json.loads without error.
* main:
  fix(template): align vite service node image with Dockerfile
  chore(deps): upgrade dependencies
  chore(template): bump django-celery-beat to 2.9+ for Django 6.0 support (#67)
Copilot AI review requested due to automatic review settings June 7, 2026 06:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

}
}
},
"postCreateCommand": "{% if dependency_manager == 'uv' %}uv sync && uv run python manage.py migrate{% else %}poetry config virtualenvs.in-project true && poetry install && poetry run python manage.py migrate{% endif %}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Docs]: Where is the devcontainer suppport?

2 participants