Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.env
.git/
docker-compose.override.yml
logs/
README.md
CLAUDE.md
docs/
*.md
!extensions.txt
46 changes: 46 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# anydev environment configuration
# Copy this file to .env and fill in the values:
# cp .env.example .env

# ---------- Code Server ----------

# Host port for the Code Server web UI
CODE_SERVER_PORT=8080

# Password for the Code Server login screen (required)
CODE_SERVER_PASSWORD=

# ---------- Host Paths ----------

# Absolute path to your code directory on the host
# This is mounted read/write into the container at /home/coder/code
HOST_CODE_DIR=/home/your-username/code

# ---------- User Identity ----------

# Must match your WSL2 user's UID/GID so file ownership is correct
# Find with: id -u and id -g
USER_UID=1000
USER_GID=1000

# ---------- Git ----------

# Used to configure git inside the container
GIT_USER_NAME=Your Name
GIT_USER_EMAIL=you@example.com

# ---------- SSH ----------

# Path to your SSH agent socket for key forwarding
# Find with: echo $SSH_AUTH_SOCK
# NOTE: This changes after reboots/agent restarts — update when needed
SSH_AUTH_SOCK=

# ---------- Claude Code ----------

# Claude Code authenticates via OAuth (Claude Max subscription).
# Run "claude" on the host first to complete browser login, then these
# paths are bind-mounted into the container automatically.
# Override only if your credentials live somewhere non-standard.
CLAUDE_CONFIG_DIR=~/.claude
CLAUDE_CREDENTIALS=~/.claude.json
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
logs/
.env
docker-compose.override.yml
.DS_Store
Thumbs.db
65 changes: 65 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# CLAUDE.md - Project Guide for anydev

## Project Overview

anydev is a portable, self-contained Docker-based development environment that provides browser-accessible VS Code (via code-server) with PHP 8.3, Node.js 22.x, and Python 3 pre-installed. It targets PHP/Drupal development on Windows WSL2 + Docker.

## Repository Structure

```
anydev/
├── CLAUDE.md # This file
├── README.md # Project overview and setup instructions
├── Dockerfile # Multi-layer image based on codercom/code-server
├── docker-compose.yml # Service config (ports, volumes, env vars)
├── docker-compose.override.yml.example # Optional Docker socket access
├── .env.example # Environment variable template (committed)
├── .dockerignore # Build context filtering
├── entrypoint.sh # Container startup script (git config, SSH validation)
├── extensions.txt # Declarative VS Code extension list
├── .gitignore # Ignores .env, docker-compose.override.yml, logs/
├── config/
│ └── settings.json # VS Code settings (bind-mounted into container)
└── docs/
├── product_requirements.md # Functional & non-functional requirements
└── implementation_plan.md # Detailed implementation specs, gotchas
```

## Key Architecture Decisions

- **Non-root container user:** `coder` at UID/GID matching host (default 1000)
- **Base image:** `codercom/code-server:latest` (Debian-based)
- **Extension management:** Build-time install from `extensions.txt` + named volume for persistence
- **SSH agent forwarding:** Socket mount (no private key exposure)
- **Settings:** `config/settings.json` bind-mounted, changes in VS Code write back to repo
- **Secrets via `.env`:** Gitignored; `.env.example` committed as template
- **Lando interop:** Sibling containers; Lando commands in host terminal (Docker socket opt-in)

## Build & Run

```sh
cp .env.example .env # Fill in your values
docker compose build # Build the image
docker compose up -d # Start in background
docker compose down # Stop
```

Build args: `USER_UID`, `USER_GID`, `PHP_VERSION` (default 8.3), `NODE_MAJOR` (default 22)

## Important Gotchas

1. Extensions must be installed as `coder` user, not root
2. Pre-create parent directory before bind-mounting `settings.json`
3. `SSH_AUTH_SOCK` must be set in `.env` or compose syntax breaks
4. UID mismatch between host and container causes file permission issues
5. Named volume can shadow rebuilt extensions — delete volume to refresh
6. Base image is Debian, not Ubuntu — use correct PHP PPA (Ondrej Sury)
7. Composer global packages installed as root won't appear on coder's PATH
8. Use `docker compose` (space, plugin form), not legacy `docker-compose`

## Development Conventions

- Default branch: `master`
- No CI pipeline configured
- No test suite — this is a container configuration project
- Keep secrets out of the image and repo; use `.env` for all credentials
113 changes: 113 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
FROM codercom/code-server:latest
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Using codercom/code-server:latest makes builds non-reproducible and can introduce breaking changes unexpectedly. Pin the base image to a specific version/tag (or digest) and update intentionally.

Suggested change
FROM codercom/code-server:latest
FROM codercom/code-server:4.91.1

Copilot uses AI. Check for mistakes.

ARG USER_UID=1000
ARG USER_GID=1000
ARG PHP_VERSION=8.3
ARG NODE_MAJOR=22

# --- Root operations: UID/GID adjustment and system packages ---
USER root

# Adjust coder user/group to match host UID/GID
RUN groupmod -g ${USER_GID} coder \
&& usermod -u ${USER_UID} -g ${USER_GID} coder \
&& chown -R ${USER_UID}:${USER_GID} /home/coder

# System prerequisites
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
gnupg \
lsb-release \
ca-certificates \
unzip \
wget \
&& rm -rf /var/lib/apt/lists/*

# Add Ondrej Sury PHP repository (Debian variant)
RUN curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /usr/share/keyrings/sury-php.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/sury-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" \
> /etc/apt/sources.list.d/sury-php.list

# Install PHP and extensions
RUN apt-get update && apt-get install -y --no-install-recommends \
php${PHP_VERSION}-cli \
php${PHP_VERSION}-mbstring \
php${PHP_VERSION}-xml \
php${PHP_VERSION}-curl \
php${PHP_VERSION}-zip \
php${PHP_VERSION}-gd \
php${PHP_VERSION}-intl \
php${PHP_VERSION}-mysql \
php${PHP_VERSION}-pgsql \
php${PHP_VERSION}-sqlite3 \
php${PHP_VERSION}-bcmath \
php${PHP_VERSION}-xdebug \
&& rm -rf /var/lib/apt/lists/*

# Install Composer via official installer
RUN curl -fsSL https://getcomposer.org/installer | php -- \
--install-dir=/usr/local/bin --filename=composer
Comment on lines +49 to +50
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

This RUN command downloads and executes the Composer installer directly from https://getcomposer.org/installer without any checksum or signature verification, which means a compromise of the Composer download endpoint or a TLS MITM could execute arbitrary code as root during image build. An attacker controlling this script can modify your image, inject backdoors, or exfiltrate secrets available in the build environment. To mitigate, pin a specific installer version and verify its published checksum or signature before execution, or install Composer from a distribution package manager that provides integrity verification.

Copilot uses AI. Check for mistakes.

# Add NodeSource repository
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash -
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Fetching and executing the NodeSource setup script via curl ... | bash - increases supply-chain risk. Prefer adding the repository via signed APT keyring + sources.list entry (similar to the Sury PHP repo) so the install path is auditable and avoids piping remote scripts into a shell.

Suggested change
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash -
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" \
> /etc/apt/sources.list.d/nodesource.list

Copilot uses AI. Check for mistakes.

# Install Node.js and yarn
RUN apt-get update && apt-get install -y --no-install-recommends \
nodejs \
&& npm install -g yarn \
&& rm -rf /var/lib/apt/lists/*

# Install Python 3 and pip
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
python3-pip \
python3-venv \
&& rm -rf /var/lib/apt/lists/*

# Install Drush Launcher
RUN curl -fsSL https://github.com/drush-ops/drush-launcher/releases/latest/download/drush.phar \
-o /usr/local/bin/drush \
&& chmod +x /usr/local/bin/drush
Comment on lines +69 to +71
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

This RUN step downloads drush.phar from https://github.com/drush-ops/drush-launcher/releases/latest/download/drush.phar and makes it executable without pinning a specific release or verifying its integrity, so a compromised GitHub account, release artifact, or TLS MITM could replace it with arbitrary code. Because drush runs inside the container with your user privileges (and access to your code and forwarded SSH agent), a malicious binary here would enable full compromise of the development environment. To reduce this risk, pin to a specific release URL and verify a known checksum or signature before installing the PHAR, or use a package source that provides built-in integrity verification.

Copilot uses AI. Check for mistakes.

# --- Switch to coder user for extensions and config ---
USER coder

# Configure npm global prefix for coder user (avoids needing root for npm install -g)
RUN mkdir -p /home/coder/.npm-global \
&& npm config set prefix /home/coder/.npm-global \
&& echo 'export PATH="/home/coder/.npm-global/bin:$PATH"' >> /home/coder/.bashrc
ENV PATH="/home/coder/.npm-global/bin:${PATH}"

# Install Claude Code globally as coder user
RUN npm install -g @anthropic-ai/claude-code

# Pre-create directories to prevent bind-mounts from creating them as directories
RUN mkdir -p /home/coder/.local/share/code-server/User \
&& mkdir -p /home/coder/.claude \
&& touch /home/coder/.claude.json

# Install VS Code extensions from extensions.txt
COPY extensions.txt /home/coder/extensions.txt
RUN failed_exts=""; \
while IFS= read -r ext || [ -n "$ext" ]; do \
ext=$(echo "$ext" | sed 's/#.*//;s/^[[:space:]]*//;s/[[:space:]]*$//'); \
[ -z "$ext" ] && continue; \
if ! code-server --install-extension "$ext"; then \
echo "Failed to install VS Code extension: $ext" >&2; \
failed_exts="${failed_exts} $ext"; \
fi; \
done < /home/coder/extensions.txt; \
if [ -n "$failed_exts" ]; then \
echo "One or more VS Code extensions failed to install:${failed_exts}" >&2; \
exit 1; \
fi

# Copy entrypoint script
COPY --chown=coder:coder entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

WORKDIR /home/coder/code

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

This Dockerfile sets a new ENTRYPOINT but does not set an explicit CMD, so runtime behavior depends entirely on whatever CMD the upstream codercom/code-server image happens to define. To make the image self-contained (and resilient to upstream changes), set an explicit CMD for starting code-server (or have the entrypoint invoke code-server directly and treat inherited CMD as args).

Suggested change
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["code-server"]

Copilot uses AI. Check for mistakes.
CMD ["code-server", "--bind-addr", "0.0.0.0:8080", "."]
Loading