Build #18 - Security Configuration: Non-Root User Setup
Date: 2025-10-27 Issue: Current Dockerfile runs all services as root (CRITICAL SECURITY ISSUE) Solution: Create non-root user with zsh/oh-my-zsh and configurable sudo access
Current State Analysis
❌ SECURITY ISSUES (Build #17 and earlier)
# Stage 6: Runtime Image
FROM node:20-slim # ← Runs as root by default
# All services run as root:
RUN apt-get update && apt-get install -y ... # ← root
COPY .claude /app/.claude # ← owned by root
CMD ["/app/start.sh"] # ← runs as root
Problems:
- ✗ All processes run with root privileges
- ✗ No user isolation
- ✗ Violates principle of least privilege
- ✗ Security risk if container compromised
- ✗ No zsh/oh-my-zsh for developer experience
Proposed Solution
✅ SECURE USER CONFIGURATION
# Stage 6: Runtime Image (NGINX + Node.js + Rust Binaries + Dev Tools)
# ============================================================================
FROM node:20-slim
# Install NGINX, system dependencies, development tools, sudo, and zsh
RUN apt-get update && apt-get install -y \
build-essential jq wget tree htop vim nano \
git-lfs ripgrep fzf tmux rsync zip unzip silversearcher-ag \
nginx curl ca-certificates \
python3 python3-pip python3-venv python3-dev \
sudo zsh \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user 'coditect' with configurable sudo access
# UID/GID 1000 (standard for first user, matches most dev machines)
ARG USER_NAME=coditect
ARG USER_UID=1000
ARG USER_GID=1000
ARG ENABLE_SUDO=true
RUN groupadd --gid $USER_GID $USER_NAME \
&& useradd --uid $USER_UID --gid $USER_GID -m -s /bin/zsh $USER_NAME \
&& if [ "$ENABLE_SUDO" = "true" ]; then \
echo "$USER_NAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/$USER_NAME \
&& chmod 0440 /etc/sudoers.d/$USER_NAME; \
fi
# Install oh-my-zsh for coditect user
USER $USER_NAME
WORKDIR /home/$USER_NAME
RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \
&& echo 'export ZSH="$HOME/.oh-my-zsh"' >> ~/.zshrc \
&& echo 'ZSH_THEME="robbyrussell"' >> ~/.zshrc \
&& echo 'plugins=(git docker kubectl zsh-autosuggestions zsh-syntax-highlighting)' >> ~/.zshrc \
&& echo 'source $ZSH/oh-my-zsh.sh' >> ~/.zshrc \
&& echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc
# Clone zsh plugins
RUN git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions \
&& git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting
# Switch back to root for installation tasks
USER root
WORKDIR /app
# Copy V5 Frontend build
COPY --from=frontend-builder /build/dist /app/v5-frontend
# Copy complete theia build
COPY --from=theia-builder /build /app/theia
# Copy all Rust binaries from build stages
COPY --from=v5-backend-builder /build/backend/target/release/api-server /usr/local/bin/coditect-v5-api
COPY --from=codi2-builder /build/codi2/target/release/codi2 /usr/local/bin/codi2
COPY --from=monitor-builder /build/file-monitor/target/release/examples/monitor /usr/local/bin/file-monitor
# Make binaries executable
RUN chmod +x /usr/local/bin/coditect-v5-api \
/usr/local/bin/codi2 \
/usr/local/bin/file-monitor
# Copy .claude directory and .coditect configs
COPY .claude /app/.claude
COPY archive/claude-code-initial-setup/.claude /app/.coditect
COPY .claude/agents /app/.coditect/agents-t2
COPY .claude/skills /app/.coditect/skills-t2
COPY .claude/commands /app/.coditect/commands-t2
COPY .claude/hooks /app/.coditect/hooks
# Create log directories and set ownership
RUN mkdir -p /app/.claude/logs /app/.coditect/logs \
/var/log/codi2 /var/log/monitor \
/workspace \
&& chown -R $USER_NAME:$USER_NAME /app /workspace /var/log/codi2 /var/log/monitor
# Install Node.js global packages (as root, accessible to all users)
RUN npm install -g --force \
typescript ts-node @types/node \
eslint prettier \
vite esbuild webpack webpack-cli \
pnpm yarn \
http-server nodemon concurrently \
react-devtools create-react-app create-next-app \
@google/gemini-cli
# Verify critical files
RUN echo "Checking for Coditect branding module..." && \
ls -la /app/theia/lib/browser/coditect-branding-frontend-module.js || \
echo "⚠️ WARNING: Coditect branding module not found!"
RUN echo "Checking for icon theme assets..." && \
find /app/theia -name "*seti*" -o -name "*vscode-icons*" | head -5 || \
echo "⚠️ WARNING: Icon theme assets not found!"
# Verify Rust binaries
RUN echo "Verifying Rust binaries..." && \
coditect-v5-api --version || echo "✓ V5 API binary ready" && \
codi2 --version && \
file-monitor --help || echo "✓ File monitor binary ready"
# Verify .coditect configs
RUN echo "Checking for .coditect configs..." && \
ls -la /app/.coditect/ && \
echo "Base configs:" && ls -1 /app/.coditect/agents/*.md | wc -l && echo "agents" && \
echo "T2 configs:" && ls -1d /app/.coditect/agents-t2/*.md /app/.coditect/skills-t2/*/ /app/.coditect/commands-t2/*.md 2>/dev/null | wc -l && echo "total files" || \
echo "⚠️ WARNING: .coditect configs incomplete!"
# Copy NGINX configuration
COPY nginx-combined.conf /etc/nginx/sites-available/default
# Expose port 80 (NGINX)
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost/health || exit 1
# Copy startup script and set ownership
COPY start-combined.sh /app/start.sh
RUN chmod +x /app/start.sh \
&& chown $USER_NAME:$USER_NAME /app/start.sh
# Switch to non-root user for running services
USER $USER_NAME
WORKDIR /workspace
CMD ["/app/start.sh"]
Updated start-combined.sh
#!/bin/bash
set -e
echo "Starting coditect-combined-v5 as user: $(whoami)"
# Start theia in background (using bundled backend for ESM compatibility)
echo "Starting theia IDE on port 3000..."
cd /app/theia
NODE_ENV=production \
THEIA_DEFAULT_PLUGINS=local-dir:/app/theia/plugins \
node lib/backend/main.js /workspace --hostname=0.0.0.0 --port=3000 &
THEIA_PID=$!
echo "theia started with PID $THEIA_PID"
# Wait for theia to be ready
echo "Waiting for theia to start..."
sleep 3
for i in {1..60}; do
if curl -sf http://localhost:3000/ > /dev/null 2>&1; then
echo "theia is ready!"
break
fi
if [ $i -eq 60 ]; then
echo "ERROR: theia failed to start within 60 seconds"
exit 1
fi
sleep 1
done
# Start CODI2 Monitoring System (as current user, NOT root)
echo "Starting CODI2 monitoring system..."
if [ -f /usr/local/bin/codi2 ]; then
/usr/local/bin/codi2 > /var/log/codi2/codi2.log 2>&1 &
CODI2_PID=$!
echo "CODI2 started with PID $CODI2_PID"
else
echo "WARNING: CODI2 binary not found at /usr/local/bin/codi2"
fi
# Start File Monitor (as current user, NOT root)
echo "Starting file monitor..."
if [ -f /usr/local/bin/file-monitor ]; then
/usr/local/bin/file-monitor > /var/log/monitor/monitor.log 2>&1 &
MONITOR_PID=$!
echo "File monitor started with PID $MONITOR_PID"
else
echo "WARNING: File monitor binary not found at /usr/local/bin/file-monitor"
fi
# Start NGINX in foreground (requires sudo since it binds to port 80)
echo "Starting NGINX (requires sudo for port 80)..."
sudo nginx -g "daemon off;"
Configuration Options
Environment Variables (K8s Deployment)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: coditect-combined
spec:
template:
spec:
containers:
- name: combined
image: us-central1-docker.pkg.dev/serene-voltage-464305-n2/coditect/coditect-combined:BUILD_ID
env:
# User configuration
- name: USER_NAME
value: "coditect" # Default: coditect
- name: USER_UID
value: "1000" # Default: 1000
- name: USER_GID
value: "1000" # Default: 1000
- name: ENABLE_SUDO
value: "true" # Default: true (set to "false" for production hardening)
# Shell configuration
- name: SHELL
value: "/bin/zsh"
- name: ZSH_THEME
value: "robbyrussell" # Can be changed to powerlevel10k, agnoster, etc.
Build Arguments (Docker Build Time)
# Default build (sudo enabled)
docker build -t coditect-combined .
# Production build (sudo disabled)
docker build \
--build-arg ENABLE_SUDO=false \
-t coditect-combined:prod .
# Custom user
docker build \
--build-arg USER_NAME=myuser \
--build-arg USER_UID=1001 \
--build-arg USER_GID=1001 \
-t coditect-combined:custom .
Security Benefits
✅ Improvements
-
Principle of Least Privilege
- Services run as non-root user
coditect - Only NGINX needs sudo (for port 80)
- Reduced attack surface
- Services run as non-root user
-
Configurable Sudo Access
ENABLE_SUDO=truefor development (default)ENABLE_SUDO=falsefor production hardening- Explicit control via environment variable
-
User Isolation
- Dedicated UID/GID (1000 by default)
- Matches standard developer machine UID
- Proper file ownership
-
Developer Experience
- Zsh with oh-my-zsh
- Auto-suggestions and syntax highlighting
- Git, Docker, kubectl plugins
-
Audit Trail
- Clear user identity in logs
whoamishowscoditect, notroot- Better compliance for CODI2 monitoring
Migration Path
Phase 1: Add User Setup (Build #18)
- Add user creation and zsh/oh-my-zsh installation
- Keep services running as root temporarily
- Verify user exists and shell works
Phase 2: Switch Services to User (Build #19)
- Update
start-combined.shto useUSER coditect - Test all services (theia, CODI2, MONITOR)
- Verify NGINX with sudo
Phase 3: Production Hardening (Build #20)
- Set
ENABLE_SUDO=falsein K8s deployment - Test without sudo access
- Final security audit
Cross-Check Log
| Aspect | Current (Build #17) | Proposed (Build #18) | Status |
|---|---|---|---|
| User | root | coditect (UID 1000) | ✅ Fixed |
| Shell | bash | zsh + oh-my-zsh | ✅ Enhanced |
| Sudo | Always available | Configurable (ENABLE_SUDO) | ✅ Controlled |
| File Ownership | root:root | coditect:coditect | ✅ Isolated |
| NGINX | root | sudo (only for port 80) | ✅ Minimal privilege |
| theia | root | coditect | ✅ Non-root |
| CODI2 | root | coditect | ✅ Non-root |
| MONITOR | root | coditect | ✅ Non-root |
| Logs | /app/.claude/logs | /var/log/codi2, /var/log/monitor | ✅ Organized |
| workspace | /app | /workspace | ✅ Dedicated |
Testing Checklist
Build #18 Validation
# 1. Build image
gcloud builds submit --config cloudbuild-combined.yaml
# 2. Deploy to test pod
kubectl apply -f k8s-theia-statefulset.yaml
# 3. Verify user setup
kubectl exec -it coditect-combined-0 -- whoami
# Expected: coditect
# 4. Verify shell
kubectl exec -it coditect-combined-0 -- zsh -c 'echo $SHELL'
# Expected: /bin/zsh
# 5. Verify oh-my-zsh
kubectl exec -it coditect-combined-0 -- ls -la ~/.oh-my-zsh
# Expected: Directory exists with themes and plugins
# 6. Verify sudo access (if ENABLE_SUDO=true)
kubectl exec -it coditect-combined-0 -- sudo whoami
# Expected: root
# 7. Verify services running
kubectl exec -it coditect-combined-0 -- ps aux | grep -E "(theia|codi2|file-monitor|nginx)"
# Expected: All running as coditect (except nginx master as root)
# 8. Verify file ownership
kubectl exec -it coditect-combined-0 -- ls -la /app/.claude
# Expected: coditect:coditect
# 9. Test theia access
curl https://coditect.ai/theia
# Expected: 200 OK
# 10. Test terminal in theia
# Open theia → terminal → New terminal
# Expected: zsh prompt with oh-my-zsh theme
Rollback Plan
If issues occur:
# 1. Revert to Build #17
kubectl set image statefulset/coditect-combined \
combined=us-central1-docker.pkg.dev/serene-voltage-464305-n2/coditect/coditect-combined:f1866abe-dbc3-4e14-9d8b-60a0a8fbeed4
# 2. Verify pods restart
kubectl get pods -n coditect-app -w
# 3. Validate Build #17 working
curl https://coditect.ai/theia
Next Steps
- ✅ Document security configuration (this file)
- ⏳ Update dockerfile.combined-fixed with user setup
- ⏳ Update start-combined.sh for non-root execution
- ⏳ Update K8s manifests with environment variables
- ⏳ Build and deploy Build #18
- ⏳ Validate all services running as coditect
- ⏳ Test sudo access configuration
- ⏳ Document results
Status: Security configuration defined, ready for implementation