Developer Environment
This document describes how developers work in this repository using VS Code Dev Containers and Docker Compose. It references files/paths rather than embedding configuration snippets.
Goals
- Consistent, reproducible tooling across machines
- Per-service runtime isolation (each service image defines its own Python/Node deps)
- Fast iteration with profile-based Compose runs
Key files
trade_psykl.code-workspace— multi-root workspace for per-service Python venv isolation. Open this file in VS Code for proper import resolution..devcontainer/devcontainer.json— editor/tooling container (Python 3.12, Node LTS, Docker CLI). Services still run as separate containers.docker-compose.yml— multi-service composition with profiles (api,engine,ui,docs,data, plus groupedweb,observability,all). Docs run natively in devcontainer for live reload (port 5173); container profile is for production builds only. Engine maps host port 8001 to container 8080 to avoid conflicts..env— default Compose profiles fordocker compose up(seeCOMPOSE_PROFILES)..vscode/tasks.json— one-click tasks to start/stop stacks and run quick tests..gitattributes,.editorconfig— line endings and formatting conventions (LF-only; consistent across OSes).docs/GETTING-STARTED.md,docs/devops/docker.md,docs/devops/docker-profiles.md— runtime and Docker guidance.
Developer dependencies (host)
These are required on your machine; the app services themselves still run in containers.
- Windows 10/11 with WSL 2 enabled (or macOS/Linux). On Windows, prefer WSL 2 for performance.
- Docker Desktop with the WSL 2 backend enabled. Turn on “WSL Integration” for your distro.
- A WSL distribution (Ubuntu 22.04+ recommended) when on Windows.
- Visual Studio Code with the “Dev Containers” extension.
- Git (Git for Windows or your platform package).
- Optional: PowerShell 7+ (pwsh) for consistent shell behavior on Windows.
Notes
- You do not need host-level Python or Node; the devcontainer supplies the CLI tooling and each service image supplies its own runtime dependencies.
- Ensure Docker Desktop is running before using Compose or reopening in the devcontainer.
Dev Container usage
- Open the repo in VS Code and use "Reopen in Container". The container provides Python/Node tooling, Docker CLI access, and installs
pre-commithooks on start. - Important: After opening in the devcontainer, open the workspace file:
File > Open Workspace from File…→trade_psykl.code-workspace. This enables per-service Python import resolution (see "Multi-root workspace" section below). - Git identity is inherited by mounting your host
~/.gitconfiginto the container. - Do not add service dependencies to the devcontainer. Each service image defines its own dependencies and Python/Node versions.
Devcontainer configuration details
This section explains what the devcontainer applies and why. See .devcontainer/devcontainer.json for the authoritative configuration.
Image
image: mcr.microsoft.com/devcontainers/python:3.12- Rationale: stable base with Python 3.12 and Debian trixie; we add Node and Docker CLI via features.
User
remoteUser: vscode- Rationale: non-root user for predictable file ownership and better security.
Features
ghcr.io/devcontainers/features/node:1with{ version: "lts" }- Provides Node LTS for UI tooling and docs tasks inside the devcontainer.
ghcr.io/devcontainers/features/docker-outside-of-docker:1with{ moby: false }- Installs Docker CE CLI and forwards the host Docker socket into the container, so you can run
docker/composeinside the devcontainer.moby: falseis required on Debian trixie (Moby packages were removed).
- Installs Docker CE CLI and forwards the host Docker socket into the container, so you can run
Lifecycle commands
postCreateCommand: python -m pip install --upgrade pip ruff black pre-commit && bash scripts/dev/bootstrap_venvs.sh- Tools-only install (pip/ruff/black/pre-commit) and per-service virtualenv bootstrap for editor import resolution.
- We intentionally do not install per-service deps in the devcontainer; those live in service images.
- The bootstrap script creates local
.venvdirectories undersrc/apiandsrc/engineand installs theirrequirements.txtso Pylance can resolve imports without polluting the devcontainer.
postStartCommand: pre-commit install || true; ... ln -s host-home/.gitconfig- Ensures Git hooks are active on every start and symlinks host Git config if present (idempotent and safe).
Environment
containerEnv.LOCAL_WORKSPACE_FOLDER = ${localWorkspaceFolder}- Lets
docker composerun inside the devcontainer with host-safe bind mounts that reference the host path.
- Lets
containerEnv.PIP_DISABLE_PIP_VERSION_CHECK = 1- Silences pip update notices to keep the terminal clean.
remoteEnv.PYTHONUNBUFFERED = 1- Ensures Python logs flush immediately (useful for streaming logs).
Mounts
mounts: [ source=${localEnv:USERPROFILE}, target=/home/vscode/host-home, type=bind ]- Exposes your Windows user profile into the container at
/home/vscode/host-home. On start, we symlink.gitconfigfrom there if it exists.
- Exposes your Windows user profile into the container at
VS Code customizations
customizations.vscode.settings- Python: interpreter path default, Pylance
typeCheckingMode: basic,blackas formatter,formatOnSave: true. - Testing:
python.testing.pytestEnabled: true,unittestEnabled: false, and default pytest args-q.
- Python: interpreter path default, Pylance
customizations.vscode.extensions- Curated set for Python, Docker, Markdown, GitHub, Jupyter, YAML, etc. Extensions run in the devcontainer so tools match the container’s environment.
Ports
forwardPorts: [8000, 5173, 8080, 27017]- Convenience forwards for API, UI dev server, native VitePress (5173), and MongoDB.
- VitePress native dev server (5173) for live reload; container (8080) for production builds.
Rebuild vs. Restart
- Rebuild required when you change:
image,features,mounts,containerEnv,forwardPorts, or VS Codecustomizations. - Restart is enough when you change:
postStartCommandonly. - No rebuild for:
.vscode/tasks.json,.env, docs, and application source (unless features/image changed).
Security and privacy
- The devcontainer mounts your host user profile read-only for Git identity discovery. Only a symlink to
.gitconfigis created if it exists; no secrets are copied. Remove the mount if you prefer to set Git identity inside the container.
Running services
- Use Compose profiles to run only what you need. The default stack (
web,observability) is set in.env. - Recommended: use VS Code tasks under
.vscode/tasks.jsonto start/stop stacks and follow logs. - For details on profiles and combinations, see
docs/GETTING-STARTED.mdanddocs/devops/docker-profiles.md.
Documentation development workflow
VitePress runs natively in the devcontainer for development with live reload. The docs container serves pre-built static files for production.
Development (live reload):
cd /workspaces/trade_psykl/docs
npm install # First time only
npm run docs:dev -- --host 0.0.0.0Or use VS Code task: Docs: Dev Server (Native)
Access at: http://localhost:8080
- ✅ Live reload - changes auto-update in browser
- ✅ Fast startup
- ✅ File watching via polling (WSL2 compatible)
Production container (static build):
The docs container builds static files and serves them with VitePress preview server:
docker compose build docs # Builds static files during image creation
docker compose --profile docs up -dAccess at: http://localhost:8080
Key differences:
| Aspect | Native Dev | Container Prod |
|---|---|---|
| Use case | Documentation editing | Deployment, CI/CD |
| Build | On-demand by Vite | Pre-built during docker build |
| Live reload | ✅ Yes | ❌ No (static files) |
| Performance | Fast iteration | Fast serving |
| Requirements | Node.js in devcontainer | Docker only |
Why native for dev?
Docker bind mounts from /workspaces/ in WSL2 devcontainers don't reliably propagate file system events. Running VitePress natively with polling-based file watching solves this and provides full HMR (Hot Module Reload).
Multi-root workspace
TL;DR: Open trade_psykl.code-workspace instead of the folder root for proper Python import resolution.
Why multi-root?
Each service (API, Engine) has its own Python dependencies isolated in a local .venv. Opening the repo as a single folder causes Pylance to use one interpreter for all files, resulting in import errors in service code (e.g., FastAPI not found in Engine).
How it works
trade_psykl.code-workspacedefines three workspace roots:- Root folder (
📁 trade_psykl (root)) — docs, docker-compose, infrastructure - API folder (
🔌 API Service) —src/apiwith its own.vscode/settings.json→.venv - Engine folder (
⚙️ Engine Service) —src/enginewith its own.vscode/settings.json→.venv
- Root folder (
On devcontainer build,
postCreateCommandrunsscripts/dev/bootstrap_venvs.sh:- Creates
src/api/.venvand installssrc/api/requirements.txt - Creates
src/engine/.venvand installssrc/engine/requirements.txt - These venvs are for editor import resolution only (services run in containers with their own dependencies)
- Creates
Each service folder's
.vscode/settings.jsonpointspython.defaultInterpreterPathto its local.venv/bin/pythonPylance resolves imports per workspace root using the corresponding venv
Opening the workspace
- First time:
File > Open Workspace from File…→trade_psykl.code-workspace - Later: Recent workspaces list shows
trade_psykl (Workspace)— select that - Indicator: VS Code title bar shows "trade_psykl (Workspace)" instead of just the folder name
Without the workspace
If you open the repo as a regular folder, you'll see import errors in service code:
- ❌
Import "fastapi" could not be resolved - ❌
Import "opentelemetry" could not be resolved
Solution: Open the workspace file.
Re-bootstrapping venvs
If imports break after dependency changes:
- Run task:
Terminal > Run Task… > Dev: Bootstrap venvs - Or rebuild the devcontainer (slower but comprehensive)
Future services
When adding new Python services (e.g., src/worker):
- Add folder to
trade_psykl.code-workspacefolders array - Create
src/worker/.vscode/settings.jsonwithpython.defaultInterpreterPath: "${workspaceFolder}/.venv/bin/python" - Update
scripts/dev/bootstrap_venvs.shto create venv and install requirements - Rebuild devcontainer or run bootstrap task
Testing
- Preferred: run tests inside the relevant service container to match its dependencies (see the test tasks in
.vscode/tasks.json). - Optional: the devcontainer enables pytest discovery in VS Code. If you want Test Explorer to execute tests locally in the devcontainer, ensure the required test dependencies are available to that environment or use container-executed test tasks.
Pre-commit hooks
- Configuration lives in
.pre-commit-config.yaml. - Hooks include common hygiene and Python tooling:
- trailing whitespace, end-of-file fixer, mixed-line-ending (forced to LF)
- check-merge-conflict, check-yaml, check-json, check-added-large-files (500 KB)
- black (Python 3.12), ruff with
--fixand--exit-non-zero-on-fix
- Usage inside the devcontainer:
- On first setup, run
pre-commit run --all-filesto format/lint the repo. - On every commit, hooks run automatically. If committing on the host, ensure
pre-commitis installed there (e.g., viapipx install pre-commit) or commit from inside the devcontainer. As a last resort, use--no-verifyto bypass hooks for that commit.
- On first setup, run
Line endings and formatting
.gitattributesenforces LF line endings;.editorconfigconfigures editors to write LF and a consistent style.- If you encounter line-ending warnings, normalize the working tree using
git add --renormalize .(see Git history for the normalization commit).
Windows notes
- For best bind-mount performance, use Docker Desktop with the WSL 2 backend and keep the repository under WSL. The composition uses host-safe bind mounts so it works from both host and devcontainer.
Next steps
- The repository includes
.pre-commit-config.yaml; the devcontainer installs and activatespre-commithooks automatically. - If you later want a production-like base
docker-compose.yml, introduce adocker-compose.dev.ymloverride for dev-only commands/mounts and run with both files.