Skip to content

Instantly share code, notes, and snippets.

@karthiks
Last active April 30, 2026 05:38
Show Gist options
  • Select an option

  • Save karthiks/df6787aa7a345c6eb88411495eaf1cbe to your computer and use it in GitHub Desktop.

Select an option

Save karthiks/df6787aa7a345c6eb88411495eaf1cbe to your computer and use it in GitHub Desktop.
Dockerfile to setup dev environment for Claude Code to build Expo / React Native app
# --- Stage 1: The JDK Provider ---
FROM eclipse-temurin:17-jdk-jammy AS jdk-source
# --- Stage 2: Final Development Image ---
FROM node:20-slim
# Build arguments for user configuration
ARG USERNAME=ccagent
ARG WORKDIR=app
# Environment variables
ENV NODE_USER=$USERNAME
ENV JAVA_HOME=/opt/java/openjdk
ENV ANDROID_HOME=/home/$USERNAME/android-sdk
ENV PATH="/home/$USERNAME/.local/bin:${JAVA_HOME}/bin:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${PATH}"
# ============================================
# Step 1: Install Minimal System Dependencies
# Watchman is needed for RN
# Leverage OMZsh with zsh shell instead of Bash for productivity
# ripgrep is better grep utility that respects .gitignore, .claudeignore and stuff.
# `usbutils` and `android-tools-adb` required for connecting emulator/devices to expo server in Docker
# ============================================
RUN apt-get update && apt-get install -y \
curl wget unzip watchman git procps \
sudo zsh ripgrep vim jq ca-certificates \
usbutils android-tools-adb \
&& rm -rf /var/lib/apt/lists/*
# ============================================
# Step 2: Setup Java
# ============================================
# Copy OpenJDK from Stage 1 (saves ~400MB of apt-install overhead)
COPY --from=jdk-source $JAVA_HOME $JAVA_HOME
# ============================================
# Step 3: User Configuration
# Rename existing user 'node' to $USERNAME to maintain UID 1000
# groupmod -n, --new-name NEW_GROUP: to rename a group
# usermod -l, --login NEW_LOGIN: to rename user account
# usermod -m, --move-home: Moves the contents of the current home directory to the new directory (used with -d).
# usermod -d, --home HOME_DIR: Changes the user's home directory to a new path.
# usermod -s, --shell SHELL: Changes the user’s login shell (e.g., /bin/bash or /sbin/nologin).
# ============================================
RUN groupmod -n $USERNAME node \
&& usermod -l $USERNAME -m -d /home/$USERNAME node \
# Add user to sudoers with NOPASSWD
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME \
&& mkdir -p $ANDROID_HOME \
&& chown -R $USERNAME:$USERNAME $ANDROID_HOME
# ============================================
# Step 4: System Prep (Root)
# ============================================
# Set zsh as the system default for the non-root user (adjust user name as needed)
RUN chsh -s $(which zsh) $USERNAME && \
mkdir -p /$WORKDIR && chown $USERNAME:$USERNAME /$WORKDIR
# ============================================
# Step 5: Grouped Android SDK Minimal Installation (As User)
# We create the dir and change ownership so the user can manage it later
# ============================================
USER $USERNAME
WORKDIR /home/$USERNAME
RUN mkdir -p $ANDROID_HOME/cmdline-tools \
&& wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/tools.zip \
&& unzip /tmp/tools.zip -d $ANDROID_HOME/cmdline-tools \
&& mv $ANDROID_HOME/cmdline-tools/cmdline-tools $ANDROID_HOME/cmdline-tools/latest \
&& rm /tmp/tools.zip \
&& yes | sdkmanager --licenses \
&& sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0"
# ============================================
# Step 6: Setup Workspace
# ============================================
USER $USERNAME
WORKDIR /$WORKDIR
# If step-4 fails, this should fix it
# Create a small bashrc that hands interactive bash sessions over to zsh
RUN echo 'if [ -t 1 ]; then\n exec /bin/zsh\nfi' > /home/$USERNAME/.bashrc
# ============================================
# Step 7: Install Productivity Tools (Oh My Zsh & Shell Config)
# Since you volume mount your project from host into /$WORKDIR, the file ownership inside the container might appear as 'root'. Thus,
# it is often safer to use a wildcard like '*' so Git doesn't block you if you mount multiple projects or submodules.
# ============================================
RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \
# Ensure our ENV PATH is preserved in interactive zsh sessions
&& echo 'export PATH=$PATH' >> ~/.zshrc \
&& git config --global core.fileMode false \
&& git config --global --add safe.directory '*' \
# Add a helper alias for headless ADB connection
&& echo 'alias adb-sync="adb connect host.docker.internal:5555"' >> /home/$USERNAME/.zshrc
# ============================================
# Step 8: Install npm global packages
# ============================================
# Configure npm to install global packages in user's home directory
RUN mkdir -p ~/.local/bin \
&& npm config set prefix ~/.local \
&& npm install -g npm@latest \
# Install EAS CLI (via NPM - Required for Expo workflow), TypeScript and the Language Server globally
&& npm install -g eas-cli typescript typescript-language-server
# ============================================
# Step 9: Install Claude Code NATIVE (Recommended for 2026)
# ============================================
RUN curl -fsSL https://claude.ai/install.sh | bash
# ============================================
# Step 10: Set Default User and Entrypoint
# ============================================
USER $USERNAME
WORKDIR /$WORKDIR
# Copy the health-check script
COPY --chown=$USERNAME:$USERNAME ./scripts/healthcheck.sh /usr/local/bin/healthcheck
RUN chmod +x /usr/local/bin/healthcheck
# Not fit for this dev environment.
#ENTRYPOINT ["zsh", "-i"]
# The container will stay alive so you can exec into it on multiple tabs (helpful for RN dev workflow)
CMD ["tail", "-f", "/dev/null"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment