Skip to content

Commit c217029

Browse files
authored
chore(devcontainer): add SSH server to devcontainer base image (#2704)
1 parent 28f1eb0 commit c217029

3 files changed

Lines changed: 54 additions & 1 deletion

File tree

.devcontainer/devcontainer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,8 @@
7676
"memory": "8gb",
7777
"storage": "32gb"
7878
},
79+
// Start sshd at every container start so `gh codespace ssh` works without an editor attach
80+
// (postAttachCommand only fires when an editor client attaches)
81+
"postStartCommand": "bash .devcontainer/start-sshd.sh",
7982
"postAttachCommand": "bash .devcontainer/setup.sh"
8083
}

.devcontainer/start-sshd.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
# Starts the SSH daemon so `gh codespace ssh` (and other SSH clients) can connect.
3+
#
4+
# The Dockerfile's own ENTRYPOINT (docker-init.sh) also starts sshd, but Codespaces/
5+
# devcontainer tooling overrides the container's entrypoint at container-start time,
6+
# so that logic never runs there. This script is wired to postStartCommand so sshd
7+
# comes up at every container start — with or without an editor attach.
8+
set -e
9+
10+
if pgrep -x sshd > /dev/null; then
11+
echo "✅ SSH daemon already running"
12+
exit 0
13+
fi
14+
15+
echo "🔑 Starting SSH daemon..."
16+
# Regenerate host keys if missing (never baked into the shared image)
17+
sudo ssh-keygen -A > /dev/null
18+
# Privilege-separation dir; /run can be tmpfs, wiping the build-time mkdir
19+
sudo mkdir -p /run/sshd
20+
sudo /usr/sbin/sshd
21+
echo "✅ SSH daemon started successfully"

.github/.devcontainer/Dockerfile

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,37 @@ RUN ARCH=$(dpkg --print-architecture) \
171171
&& apt-get clean \
172172
&& rm -rf /var/lib/apt/lists/*
173173

174-
# Create docker startup script
174+
# Install and configure OpenSSH server (replaces the ghcr.io/devcontainers/features/sshd feature).
175+
# Host keys must never be baked into this shared base image (every container/Codespace built
176+
# from it would otherwise share one static key pair), so they are stripped here and regenerated
177+
# fresh at container startup instead, by docker-init.sh via `ssh-keygen -A`.
178+
RUN apt-get update \
179+
&& apt-get install -y --no-install-recommends openssh-server openssh-sftp-server \
180+
&& mkdir -p /run/sshd \
181+
&& sed -i -E \
182+
-e 's/^#?Port .*/Port 2222/' \
183+
-e 's/^#?PasswordAuthentication .*/PasswordAuthentication no/' \
184+
-e 's/^#?PubkeyAuthentication .*/PubkeyAuthentication yes/' \
185+
-e 's/^#?UsePAM .*/UsePAM yes/' \
186+
/etc/ssh/sshd_config \
187+
&& /usr/sbin/sshd -t \
188+
&& rm -f /etc/ssh/ssh_host_* \
189+
&& apt-get clean \
190+
&& rm -rf /var/lib/apt/lists/*
191+
192+
# Create docker startup script.
193+
# NOTE: This script is the image's ENTRYPOINT, but Codespaces/devcontainer tooling
194+
# overrides the entrypoint at container start, so it never runs there — it only
195+
# runs under plain `docker run`. On Codespaces sshd is started by
196+
# .devcontainer/start-sshd.sh (postStartCommand) and dockerd by
197+
# .devcontainer/setup.sh (postAttachCommand) instead. Keep these in sync.
175198
RUN echo '#!/bin/bash\n\
199+
# Regenerate SSH host keys if missing (never baked into the shared image)\n\
200+
sudo ssh-keygen -A > /dev/null\n\
201+
# Privilege-separation dir; /run can be tmpfs, wiping the build-time mkdir\n\
202+
sudo mkdir -p /run/sshd\n\
203+
# Start the SSH daemon (self-daemonizes; do not add -D here)\n\
204+
sudo /usr/sbin/sshd\n\
176205
# Start docker daemon in background\n\
177206
sudo dockerd > /dev/null 2>&1 &\n\
178207
# Wait for docker to be ready\n\

0 commit comments

Comments
 (0)