-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
311 lines (265 loc) · 13.5 KB
/
Dockerfile
File metadata and controls
311 lines (265 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# Base image
FROM codercom/code-server:latest
USER root
# Layer 1: System tools
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
wget \
vim \
tmux \
dnsutils \
ca-certificates \
gnupg \
lsb-release \
&& rm -rf /var/lib/apt/lists/*
# Install yq
RUN ARCH=$(dpkg --print-architecture) \
&& curl -sL https://github.com/mikefarah/yq/releases/download/v4.44.3/yq_linux_${ARCH} -o /usr/bin/yq \
&& chmod +x /usr/bin/yq
# Install kubectl
RUN mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg \
&& chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg \
&& echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends kubectl \
&& rm -rf /var/lib/apt/lists/*
# Layer 2: User permissions - sudo with su blocked
RUN apt-get update && apt-get install -y --no-install-recommends sudo \
&& find /etc/sudoers.d -maxdepth 1 -type f -name '*coder*' -delete \
&& echo "coder ALL=(ALL) NOPASSWD: ALL, !/usr/bin/su, !/bin/su" > /etc/sudoers.d/zz-coder-policy \
&& chmod 440 /etc/sudoers.d/zz-coder-policy \
&& visudo -c -f /etc/sudoers.d/zz-coder-policy \
&& sudo -l -U coder | grep -Eq '!(/usr/bin/su|/bin/su)' \
&& rm -rf /var/lib/apt/lists/*
# Layer 3: Go (pinned stable) with China mirror and tools
ENV GO_VERSION=1.26.1
ENV GO_SHA256_AMD64=031f088e5d955bab8657ede27ad4e3bc5b7c1ba281f05f245bcc304f327c987a
ENV GO_SHA256_ARM64=a290581cfe4fe28ddd737dde3095f3dbeb7f2e4065cab4eae44dfc53b760c2f7
ENV GOPROXY=https://goproxy.cn,direct
# GOPATH for user packages (can be mounted), tools installed to /opt/go-tools
ENV GOPATH=/home/coder/go
ENV GO_TOOLS_PATH=/opt/go-tools
ENV PATH=/usr/local/go/bin:/opt/go-tools/bin:/home/coder/go/bin:$PATH
RUN ARCH=$(dpkg --print-architecture) \
&& case "$ARCH" in \
amd64) GO_SHA256="$GO_SHA256_AMD64" ;; \
arm64) GO_SHA256="$GO_SHA256_ARM64" ;; \
*) echo "Unsupported architecture: $ARCH" && exit 1 ;; \
esac \
&& curl -fsSL https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz -o /tmp/go.tgz \
&& echo "${GO_SHA256} /tmp/go.tgz" | sha256sum -c - \
&& rm -rf /usr/local/go \
&& tar -C /usr/local -xzf /tmp/go.tgz \
&& rm -f /tmp/go.tgz \
&& go version
# Install Go tools to /opt/go-tools (not affected by volume mounts on /home/coder)
RUN mkdir -p /opt/go-tools \
&& GOPATH=/opt/go-tools GOPROXY=${GOPROXY} /usr/local/go/bin/go install golang.org/x/tools/gopls@latest \
&& GOPATH=/opt/go-tools GOPROXY=${GOPROXY} /usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv@latest \
&& GOPATH=/opt/go-tools GOPROXY=${GOPROXY} /usr/local/go/bin/go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest \
&& GOPATH=/opt/go-tools GOPROXY=${GOPROXY} /usr/local/go/bin/go install golang.org/x/tools/cmd/goimports@latest
# Create symlinks for go commands (ensures availability even when PATH is reset)
RUN ln -s /usr/local/go/bin/go /usr/local/bin/go \
&& ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt
# Layer 4: Python + uv + conda with China mirrors
ENV UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
# Install Miniforge (conda-forge based, no Anaconda ToS required)
RUN ARCH=$(dpkg --print-architecture) && \
if [ "$ARCH" = "amd64" ]; then CONDA_ARCH="x86_64"; \
elif [ "$ARCH" = "arm64" ]; then CONDA_ARCH="aarch64"; \
else CONDA_ARCH="$ARCH"; fi && \
rm -rf /opt/conda \
&& curl -fsSL https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-${CONDA_ARCH}.sh -o /tmp/miniforge.sh \
&& chmod +x /tmp/miniforge.sh \
&& bash /tmp/miniforge.sh -b -p /opt/conda \
&& rm /tmp/miniforge.sh
ENV PATH=/opt/conda/bin:$PATH
# Install Python 3.13 via conda-forge and create symlinks
RUN /opt/conda/bin/conda install -y python=3.13 \
&& /opt/conda/bin/conda config --set show_channel_urls yes \
&& /opt/conda/bin/conda clean -afy \
&& ln -sf /opt/conda/bin/python /usr/bin/python3 \
&& ln -sf /opt/conda/bin/python /usr/bin/python \
&& ln -sf /opt/conda/bin/pip /usr/bin/pip3 \
&& ln -sf /opt/conda/bin/pip /usr/bin/pip
# Install uv (official recommended way: copy from pre-built image)
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
# Layer 5: Node.js (latest LTS) with China mirror
ENV NODE_VERSION=22
# Install Node.js from NodeSource
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/*
# Configure npm mirror and install pnpm, yarn, claude-code
RUN npm config set registry https://registry.npmmirror.com --global \
&& npm install -g pnpm yarn @anthropic-ai/claude-code@latest \
&& npm cache clean --force \
&& pnpm config set registry https://registry.npmmirror.com \
&& yarn config set registry https://registry.npmmirror.com
# Layer 6: JDK 21 (Temurin) + Maven 3.9.x with Aliyun mirror
ENV JAVA_HOME=/opt/temurin-21-jdk
ENV MAVEN_VERSION=3.9.11
ENV PATH=$JAVA_HOME/bin:$PATH
# Install JDK 21 (Eclipse Temurin) - download directly to avoid repo setup
RUN ARCH=$(dpkg --print-architecture) && \
if [ "$ARCH" = "amd64" ]; then JDK_ARCH="x64"; \
elif [ "$ARCH" = "arm64" ]; then JDK_ARCH="aarch64"; \
else JDK_ARCH="$ARCH"; fi && \
curl -fsSL "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.6%2B7/OpenJDK21U-jdk_${JDK_ARCH}_linux_hotspot_21.0.6_7.tar.gz" | tar -C /opt -xzf - && \
mv /opt/jdk-21.0.6+7 /opt/temurin-21-jdk
# Install Maven
RUN curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" | tar -C /opt -xzf - \
&& ln -s /opt/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/local/bin/mvn
# Create symlinks for java commands (ensures availability even when PATH is reset)
RUN ln -s /opt/temurin-21-jdk/bin/java /usr/local/bin/java \
&& ln -s /opt/temurin-21-jdk/bin/javac /usr/local/bin/javac \
&& ln -s /opt/temurin-21-jdk/bin/jar /usr/local/bin/jar
# Layer 7: Ruby (rbenv) + Rails with Ruby China mirror
# Install rbenv to /opt/rbenv to avoid being overwritten by volume mounts
ENV RBENV_ROOT=/opt/rbenv
ENV RUBY_VERSION=4.0.2
ENV RUBY_GEM_ABI=4.0.0
ENV PATH=$RBENV_ROOT/bin:$RBENV_ROOT/shims:$PATH
# Install Ruby dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \
bison \
build-essential \
libssl-dev \
libyaml-dev \
libreadline6-dev \
zlib1g-dev \
libncurses5-dev \
libffi-dev \
libgdbm6 \
libgdbm-dev \
&& rm -rf /var/lib/apt/lists/*
# Install rbenv and ruby-build to /opt/rbenv (system path, not affected by volume mounts)
RUN git clone --depth 1 https://github.com/rbenv/rbenv.git /opt/rbenv \
&& git clone --depth 1 https://github.com/rbenv/ruby-build.git /opt/rbenv/plugins/ruby-build
# Install latest stable Ruby and Rails
RUN /opt/rbenv/plugins/ruby-build/install.sh \
&& rbenv install "$RUBY_VERSION" \
&& rbenv global "$RUBY_VERSION" \
&& rbenv rehash \
&& gem install bundler rails --no-document \
&& rbenv rehash
# Configure gem mirror
RUN echo "---\n:sources:\n - https://gems.ruby-china.com/" > /home/coder/.gemrc
# Layer 8: Database and Middleware Clients
# PostgreSQL client (official PostgreSQL apt repository)
RUN mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /etc/apt/keyrings/postgresql-keyring.gpg \
&& chmod 644 /etc/apt/keyrings/postgresql-keyring.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/postgresql-keyring.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/postgresql.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends postgresql-client-17 \
&& rm -rf /var/lib/apt/lists/*
# Docker CLI (official Docker apt repository, client only)
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& chmod 644 /etc/apt/keyrings/docker.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends docker-ce-cli docker-buildx-plugin docker-compose-plugin \
&& rm -rf /var/lib/apt/lists/*
# MySQL and Redis clients (Debian standard packages)
RUN apt-get update && apt-get install -y --no-install-recommends \
default-mysql-client \
redis-tools \
&& rm -rf /var/lib/apt/lists/*
# Kafka CLI tools
ENV KAFKA_VERSION=3.9.0
ENV KAFKA_SCALA_VERSION=2.13
ENV KAFKA_HOME=/opt/kafka
RUN curl -fsSL "https://archive.apache.org/dist/kafka/${KAFKA_VERSION}/kafka_${KAFKA_SCALA_VERSION}-${KAFKA_VERSION}.tgz" -o /tmp/kafka.tgz \
&& mkdir -p ${KAFKA_HOME} \
&& tar -xzf /tmp/kafka.tgz -C ${KAFKA_HOME} --strip-components=1 \
&& rm -f /tmp/kafka.tgz \
&& chmod +x ${KAFKA_HOME}/bin/*
# Create symlinks for frequently used Kafka tools
RUN ln -s ${KAFKA_HOME}/bin/kafka-topics.sh /usr/local/bin/kafka-topics \
&& ln -s ${KAFKA_HOME}/bin/kafka-console-consumer.sh /usr/local/bin/kafka-console-consumer \
&& ln -s ${KAFKA_HOME}/bin/kafka-console-producer.sh /usr/local/bin/kafka-console-producer \
&& ln -s ${KAFKA_HOME}/bin/kafka-consumer-groups.sh /usr/local/bin/kafka-consumer-groups \
&& ln -s ${KAFKA_HOME}/bin/kafka-configs.sh /usr/local/bin/kafka-configs \
&& ln -s ${KAFKA_HOME}/bin/kafka-acls.sh /usr/local/bin/kafka-acls
# Layer 9: Directory structure and config files
# Create system-wide PATH config (not affected by volume mounts on /home/coder)
# This ensures tools are accessible in all shell types (login/non-login, interactive/non-interactive)
RUN echo '#!/bin/sh\n\
# Development tools PATH configuration\n\
# Note: Symlinks in /usr/local/bin provide fallback, this is additional coverage\n\
export PATH=/opt/kafka/bin:/opt/go-tools/bin:/opt/rbenv/bin:/opt/rbenv/shims:/usr/local/go/bin:/home/coder/go/bin:/opt/temurin-21-jdk/bin:/opt/conda/bin:$PATH' > /etc/profile.d/dev-tools.sh \
&& chmod +x /etc/profile.d/dev-tools.sh
# Create config templates directory (for volume mount compatibility)
# When /home/coder is mounted externally, these templates will be used to initialize configs
RUN mkdir -p /opt/dev-configs
# bashrc append content template
RUN echo '\n\
# Restore Docker ENV PATH (VS Code terminal resets PATH)\n\
export PATH=/opt/kafka/bin:/opt/go-tools/bin:/opt/rbenv/bin:/opt/rbenv/shims:/opt/temurin-21-jdk/bin:/opt/conda/bin:/usr/local/go/bin:/home/coder/go/bin:$PATH\n\
\n\
# User-installed gem executables path\n\
export RUBY_GEM_ABI=${RUBY_GEM_ABI:-4.0.0}\n\
export PATH=/home/coder/.local/share/gem/ruby/${RUBY_GEM_ABI}/bin:$PATH\n\
\n\
# Initialize rbenv\n\
if [ -x /opt/rbenv/bin/rbenv ]; then\n\
eval "$(/opt/rbenv/bin/rbenv init - bash)"\n\
fi' > /opt/dev-configs/bashrc-append.sh
# gemrc template
RUN echo '---\n:sources:\n - https://gems.ruby-china.com/' > /opt/dev-configs/gemrc
# Maven settings.xml template
RUN echo '<?xml version="1.0" encoding="UTF-8"?>\n\
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"\n\
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n\
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">\n\
<mirrors>\n\
<mirror>\n\
<id>aliyun</id>\n\
<mirrorOf>central</mirrorOf>\n\
<name>Aliyun Maven Mirror</name>\n\
<url>https://maven.aliyun.com/repository/public</url>\n\
</mirror>\n\
</mirrors>\n\
</settings>' > /opt/dev-configs/m2-settings.xml
# pip config template
RUN echo '[global]\nindex-url = https://pypi.tuna.tsinghua.edu.cn/simple' > /opt/dev-configs/pip.conf
# Copy initialization script
COPY scripts/init-home.sh /opt/dev-configs/init-home.sh
RUN chmod +x /opt/dev-configs/init-home.sh
# Create directories for external mounting support
RUN mkdir -p /home/coder/project \
&& mkdir -p /home/coder/.local/share/code-server \
&& mkdir -p /home/coder/.local/share/pnpm \
&& mkdir -p /home/coder/.m2/repository \
&& mkdir -p /home/coder/.config/pip \
&& mkdir -p /home/coder/.npm \
&& mkdir -p /home/coder/.cache/uv \
&& mkdir -p /home/coder/.cache/pip \
&& mkdir -p /home/coder/go
# Declare volumes for external mounting (optional, users can override with -v)
# These can be mounted to persist data across container restarts
VOLUME ["/home/coder/project"]
# Initialize config files from templates
RUN cp /opt/dev-configs/gemrc /home/coder/.gemrc \
&& cp /opt/dev-configs/m2-settings.xml /home/coder/.m2/settings.xml \
&& cp /opt/dev-configs/pip.conf /home/coder/.config/pip/pip.conf \
&& cat /opt/dev-configs/bashrc-append.sh >> /home/coder/.bashrc
# Set ownership for coder user
RUN chown -R coder:coder /home/coder
# Create entrypoint script that initializes home directory on container start
# This ensures configs are properly set when /home/coder is mounted externally
RUN echo '#!/bin/bash\n\
# Initialize home directory if mounted externally\n\
/opt/dev-configs/init-home.sh\n\
# Execute the original entrypoint or command\n\
exec "$@"' > /entrypoint.sh \
&& chmod +x /entrypoint.sh
USER coder
WORKDIR /home/coder/project
# Use custom entrypoint to handle volume mount initialization
ENTRYPOINT ["/entrypoint.sh"]
CMD ["code-server", "--bind-addr", "0.0.0.0:8080", "."]