Skip to content

Commit d101ef1

Browse files
Optimize docker
1 parent 620f60a commit d101ef1

File tree

6 files changed

+506
-33
lines changed

6 files changed

+506
-33
lines changed

.devcontainer/Dockerfile

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
# Use the modern Python dev container with latest Debian
2-
FROM mcr.microsoft.com/devcontainers/python:1-3.12-bookworm
1+
# syntax=docker/dockerfile:1.4
2+
3+
# Build stage for dependencies
4+
FROM mcr.microsoft.com/devcontainers/python:1-3.12-bookworm AS builder
35

46
# Set build arguments for flexibility
57
ARG VIRTUAL_ENV_PATH=/home/vscode/.venv
@@ -10,21 +12,10 @@ ENV PYTHONUNBUFFERED=1 \
1012
PYTHONDONTWRITEBYTECODE=1 \
1113
PIP_NO_CACHE_DIR=1 \
1214
PIP_DISABLE_PIP_VERSION_CHECK=1 \
13-
PYTHONPATH=/workspaces/Apim-Samples/shared/python:/workspaces/Apim-Samples \
1415
VIRTUAL_ENV=${VIRTUAL_ENV_PATH} \
1516
PATH="${VIRTUAL_ENV_PATH}/bin:$PATH"
1617

17-
# Install system dependencies as root (better caching)
18-
USER root
19-
RUN apt-get update && apt-get install -y --no-install-recommends \
20-
curl \
21-
wget \
22-
jq \
23-
tree \
24-
&& apt-get clean \
25-
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
26-
27-
# Switch to vscode user for all Python operations
18+
# Switch to vscode user for build
2819
USER vscode
2920

3021
# Create virtual environment with optimized settings
@@ -33,15 +24,56 @@ RUN python3 -m venv $VIRTUAL_ENV --copies
3324
# Copy only requirements.txt first for better Docker layer caching
3425
COPY --chown=vscode:vscode requirements.txt /tmp/requirements.txt
3526

36-
# Install Python packages with optimizations
37-
RUN set -ex && \
27+
# Install Python packages with mount cache for pip
28+
RUN --mount=type=cache,target=/home/vscode/.cache/pip,uid=1000,gid=1000 \
29+
set -ex && \
3830
. $VIRTUAL_ENV/bin/activate && \
39-
pip install --no-cache-dir --upgrade pip setuptools wheel && \
40-
pip install --no-cache-dir --compile -r /tmp/requirements.txt && \
41-
pip install --no-cache-dir --compile pytest pytest-cov coverage ipykernel && \
31+
pip install --upgrade pip setuptools wheel && \
32+
pip install --compile -r /tmp/requirements.txt && \
33+
pip install --compile pytest pytest-cov coverage ipykernel && \
4234
python -m ipykernel install --user --name=apim-samples --display-name="APIM Samples Python" && \
43-
pip list --format=freeze > /tmp/installed-packages.txt && \
44-
rm /tmp/requirements.txt
35+
pip list --format=freeze > /tmp/installed-packages.txt
36+
37+
# Production stage
38+
FROM mcr.microsoft.com/devcontainers/python:1-3.12-bookworm AS production
39+
40+
# Set build arguments for flexibility
41+
ARG VIRTUAL_ENV_PATH=/home/vscode/.venv
42+
ARG PYTHON_VERSION=3.12
43+
44+
# Set environment variables early for better caching
45+
ENV PYTHONUNBUFFERED=1 \
46+
PYTHONDONTWRITEBYTECODE=1 \
47+
PIP_NO_CACHE_DIR=1 \
48+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
49+
PYTHONPATH=/workspaces/Apim-Samples/shared/python:/workspaces/Apim-Samples \
50+
VIRTUAL_ENV=${VIRTUAL_ENV_PATH} \
51+
PATH="${VIRTUAL_ENV_PATH}/bin:$PATH" \
52+
DEBIAN_FRONTEND=noninteractive
53+
54+
# Install system dependencies as root with mount cache
55+
USER root
56+
RUN --mount=type=cache,target=/var/cache/apt \
57+
--mount=type=cache,target=/var/lib/apt/lists \
58+
apt-get update && apt-get install -y --no-install-recommends \
59+
curl \
60+
wget \
61+
jq \
62+
tree \
63+
git-lfs \
64+
vim \
65+
nano \
66+
htop \
67+
&& apt-get clean
68+
69+
# Copy the virtual environment from builder stage
70+
COPY --from=builder --chown=vscode:vscode ${VIRTUAL_ENV_PATH} ${VIRTUAL_ENV_PATH}
71+
72+
# Copy Jupyter kernel config from builder
73+
COPY --from=builder --chown=vscode:vscode /home/vscode/.local /home/vscode/.local
74+
75+
# Switch to vscode user for all remaining operations
76+
USER vscode
4577

4678
# Configure shell environment with optimizations
4779
RUN set -ex && \
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Configure Python interpreter for VS Code in devcontainer.
4+
5+
This script ensures that VS Code automatically detects and uses the virtual environment
6+
created during container build.
7+
"""
8+
9+
import json
10+
import os
11+
import subprocess
12+
import sys
13+
from pathlib import Path
14+
15+
16+
# ------------------------------
17+
# CONSTANTS
18+
# ------------------------------
19+
20+
VENV_PATH = "/home/vscode/.venv"
21+
PYTHON_EXECUTABLE = f"{VENV_PATH}/bin/python"
22+
WORKSPACE_ROOT = "/workspaces/Apim-Samples"
23+
24+
25+
# ------------------------------
26+
# PUBLIC METHODS
27+
# ------------------------------
28+
29+
def register_python_interpreter() -> bool:
30+
"""
31+
Register the virtual environment Python interpreter with VS Code.
32+
33+
Returns:
34+
bool: True if successful, False otherwise
35+
"""
36+
try:
37+
# Verify the virtual environment exists
38+
if not Path(PYTHON_EXECUTABLE).exists():
39+
print(f"❌ Python interpreter not found at {PYTHON_EXECUTABLE}")
40+
return False
41+
42+
# Get Python version info
43+
result = subprocess.run(
44+
[PYTHON_EXECUTABLE, "--version"],
45+
capture_output=True,
46+
text=True,
47+
check=True
48+
)
49+
python_version = result.stdout.strip()
50+
print(f"🐍 Found Python interpreter: {python_version}")
51+
52+
# Verify packages are installed
53+
result = subprocess.run(
54+
[PYTHON_EXECUTABLE, "-c", "import requests, jwt, pandas, matplotlib"],
55+
capture_output=True,
56+
text=True,
57+
check=True
58+
)
59+
print("✅ Core packages verified")
60+
61+
# Create/update workspace settings to point to the correct interpreter
62+
vscode_dir = Path(WORKSPACE_ROOT) / ".vscode"
63+
settings_file = vscode_dir / "settings.json"
64+
65+
# Ensure .vscode directory exists
66+
vscode_dir.mkdir(exist_ok=True)
67+
68+
# Read existing settings if they exist
69+
settings = {}
70+
if settings_file.exists():
71+
try:
72+
with open(settings_file, 'r') as f:
73+
content = f.read()
74+
# Remove comments for JSON parsing
75+
lines = content.split('\n')
76+
clean_lines = [line for line in lines if not line.strip().startswith('//')]
77+
clean_content = '\n'.join(clean_lines)
78+
settings = json.loads(clean_content)
79+
except json.JSONDecodeError:
80+
print("⚠️ Existing settings.json has syntax issues, will preserve as much as possible")
81+
82+
# Update Python interpreter settings
83+
python_settings = {
84+
"python.defaultInterpreterPath": PYTHON_EXECUTABLE,
85+
"python.pythonPath": PYTHON_EXECUTABLE,
86+
"python.terminal.activateEnvironment": True,
87+
"python.terminal.activateEnvInCurrentTerminal": True,
88+
"jupyter.defaultKernel": "apim-samples",
89+
"jupyter.askForKernelRestart": False,
90+
}
91+
92+
settings.update(python_settings)
93+
94+
print(f"✅ Python interpreter registered: {PYTHON_EXECUTABLE}")
95+
return True
96+
97+
except subprocess.CalledProcessError as e:
98+
print(f"❌ Error checking Python interpreter: {e}")
99+
return False
100+
except Exception as e:
101+
print(f"❌ Unexpected error: {e}")
102+
return False
103+
104+
105+
def create_python_environment_marker() -> None:
106+
"""
107+
Create a marker file that VS Code can use to auto-detect the environment.
108+
"""
109+
try:
110+
# Create a pyvenv.cfg file that VS Code recognizes
111+
pyvenv_cfg = Path(VENV_PATH) / "pyvenv.cfg"
112+
if not pyvenv_cfg.exists():
113+
with open(pyvenv_cfg, 'w') as f:
114+
f.write(f"home = /usr/bin\n")
115+
f.write(f"include-system-site-packages = false\n")
116+
f.write(f"version = 3.12\n")
117+
f.write(f"executable = {PYTHON_EXECUTABLE}\n")
118+
print("✅ Created Python environment marker")
119+
120+
# Create activate script marker
121+
activate_script = Path(VENV_PATH) / "bin" / "activate"
122+
if activate_script.exists():
123+
print("✅ Virtual environment activation script found")
124+
else:
125+
print("⚠️ Virtual environment activation script not found")
126+
127+
except Exception as e:
128+
print(f"⚠️ Could not create environment marker: {e}")
129+
130+
131+
def verify_setup() -> bool:
132+
"""
133+
Verify that the Python environment is properly configured.
134+
135+
Returns:
136+
bool: True if verification passes, False otherwise
137+
"""
138+
try:
139+
print("🔍 Verifying Python environment setup...")
140+
141+
# Check virtual environment
142+
if not Path(VENV_PATH).exists():
143+
print(f"❌ Virtual environment not found at {VENV_PATH}")
144+
return False
145+
146+
# Check Python executable
147+
if not Path(PYTHON_EXECUTABLE).exists():
148+
print(f"❌ Python executable not found at {PYTHON_EXECUTABLE}")
149+
return False
150+
151+
# Test Python execution
152+
result = subprocess.run(
153+
[PYTHON_EXECUTABLE, "-c", "import sys; print(f'Python {sys.version}')"],
154+
capture_output=True,
155+
text=True,
156+
check=True
157+
)
158+
print(f"✅ {result.stdout.strip()}")
159+
160+
# Test package imports
161+
packages_to_test = ["requests", "jwt", "pandas", "matplotlib", "azure.storage.blob", "azure.identity"]
162+
for package in packages_to_test:
163+
try:
164+
subprocess.run(
165+
[PYTHON_EXECUTABLE, "-c", f"import {package}"],
166+
capture_output=True,
167+
text=True,
168+
check=True
169+
)
170+
except subprocess.CalledProcessError:
171+
print(f"⚠️ Package {package} not available")
172+
return False
173+
174+
print("✅ All required packages are available")
175+
176+
# Check Jupyter kernel
177+
try:
178+
result = subprocess.run(
179+
[PYTHON_EXECUTABLE, "-m", "jupyter", "kernelspec", "list"],
180+
capture_output=True,
181+
text=True,
182+
check=True
183+
)
184+
if "apim-samples" in result.stdout:
185+
print("✅ Jupyter kernel 'apim-samples' is registered")
186+
else:
187+
print("⚠️ Jupyter kernel 'apim-samples' not found")
188+
except subprocess.CalledProcessError:
189+
print("⚠️ Could not check Jupyter kernels")
190+
191+
return True
192+
193+
except Exception as e:
194+
print(f"❌ Verification failed: {e}")
195+
return False
196+
197+
198+
def main() -> int:
199+
"""
200+
Main function to configure Python interpreter.
201+
202+
Returns:
203+
int: Exit code (0 for success, 1 for failure)
204+
"""
205+
print("🔧 Configuring Python interpreter for VS Code...")
206+
print("")
207+
208+
# Register the Python interpreter
209+
if not register_python_interpreter():
210+
print("❌ Failed to register Python interpreter")
211+
return 1
212+
213+
# Create environment markers
214+
create_python_environment_marker()
215+
216+
# Verify the setup
217+
if not verify_setup():
218+
print("❌ Environment verification failed")
219+
return 1
220+
221+
print("")
222+
print("🎉 Python interpreter configuration complete!")
223+
print(f"📍 Interpreter path: {PYTHON_EXECUTABLE}")
224+
print(f"📍 Virtual environment: {VENV_PATH}")
225+
print("")
226+
print("💡 VS Code should now automatically use the virtual environment.")
227+
print("💡 If you still see a Python interpreter selection prompt:")
228+
print(" 1. Press Ctrl+Shift+P")
229+
print(" 2. Type 'Python: Select Interpreter'")
230+
print(f" 3. Choose: {PYTHON_EXECUTABLE}")
231+
print("")
232+
233+
return 0
234+
235+
236+
if __name__ == "__main__":
237+
sys.exit(main())

.devcontainer/devcontainer.json

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,27 @@
4545
"ms-vscode.vscode-json"
4646
],
4747
"settings": {
48-
"python.defaultInterpreterPath": "~/.venv/bin/python",
48+
"python.defaultInterpreterPath": "/home/vscode/.venv/bin/python",
49+
"python.pythonPath": "/home/vscode/.venv/bin/python",
50+
"python.terminal.activateEnvironment": true,
51+
"python.terminal.activateEnvInCurrentTerminal": true,
52+
"python.envFile": "${workspaceFolder}/.env",
4953
"python.linting.enabled": true,
5054
"python.linting.pylintEnabled": true,
51-
"python.formatting.autopep8Path": "~/.venv/bin/autopep8",
52-
"python.formatting.blackPath": "~/.venv/bin/black",
53-
"python.formatting.yapfPath": "~/.venv/bin/yapf",
54-
"python.linting.banditPath": "~/.venv/bin/bandit",
55-
"python.linting.flake8Path": "~/.venv/bin/flake8",
56-
"python.linting.mypyPath": "~/.venv/bin/mypy",
57-
"python.linting.pycodestylePath": "~/.venv/bin/pycodestyle",
58-
"python.linting.pydocstylePath": "~/.venv/bin/pydocestyle",
59-
"python.linting.pylintPath": "~/.venv/bin/pylint",
60-
"python.testing.pytestPath": "~/.venv/bin/pytest",
55+
"python.formatting.autopep8Path": "/home/vscode/.venv/bin/autopep8",
56+
"python.formatting.blackPath": "/home/vscode/.venv/bin/black",
57+
"python.formatting.yapfPath": "/home/vscode/.venv/bin/yapf",
58+
"python.linting.banditPath": "/home/vscode/.venv/bin/bandit",
59+
"python.linting.flake8Path": "/home/vscode/.venv/bin/flake8",
60+
"python.linting.mypyPath": "/home/vscode/.venv/bin/mypy",
61+
"python.linting.pycodestylePath": "/home/vscode/.venv/bin/pycodestyle",
62+
"python.linting.pydocstylePath": "/home/vscode/.venv/bin/pydocestyle",
63+
"python.linting.pylintPath": "/home/vscode/.venv/bin/pylint",
64+
"python.testing.pytestPath": "/home/vscode/.venv/bin/pytest",
6165
"jupyter.askForKernelRestart": false,
6266
"jupyter.interactiveWindow.textEditor.executeSelection": true,
67+
"jupyter.defaultKernel": "apim-samples",
68+
"jupyter.notebookFileRoot": "${workspaceFolder}",
6369
"files.associations": {
6470
"*.bicep": "bicep"
6571
}

.devcontainer/post-start-light.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ az extension add --name front-door --only-show-errors 2>/dev/null || true
3232
echo "📓 Ensuring Jupyter kernel is available..."
3333
python -m ipykernel install --user --name=apim-samples --display-name="APIM Samples Python" 2>/dev/null || echo "⚠️ Jupyter kernel already configured"
3434

35+
# Ensure VS Code recognizes the Python interpreter
36+
echo "🐍 Configuring Python interpreter for VS Code..."
37+
python .devcontainer/configure-python-interpreter.py
38+
3539
# Create workspace settings if they don't exist
3640
echo "🛠️ Ensuring workspace configuration..."
3741
mkdir -p .vscode

0 commit comments

Comments
 (0)