# Alpine dev container with dropbear SSH access and this dotfiles repo # pre-cloned and installed. # # Build: # docker build -t dev-container \ # --build-arg USERNAME=dafu \ # --build-arg GITHUB_USER=dafu \ # devcontainer # # Run: # docker run -d -p 2222:22 --name dev-container dev-container # ssh -p 2222 dafu@localhost FROM alpine:latest ARG USERNAME=dafu # public keys are pulled from https://github.com/.keys at build time ARG GITHUB_USER=dafu ARG REPO_URL=https://git.dafu.dev/dafu/dot.config.git # gid of the docker group on the host, so it lines up with the mounted # /var/run/docker.sock (check with: stat -c '%g' /var/run/docker.sock) ARG DOCKER_GID=104 # edge repos, aliased so packages stay on the stable branch by default; # pull a specific package from edge with `apk add @edge` RUN cat <<'EOF' > /etc/apk/repositories https://dl-cdn.alpinelinux.org/alpine/edge/main https://dl-cdn.alpinelinux.org/alpine/edge/community https://dl-cdn.alpinelinux.org/alpine/edge/testing EOF RUN apk update RUN apk add --no-cache \ dropbear htop fd fzf fzf-tmux gnupg age go-task gopass ripgrep lnav lazygit mysql-client \ nmap p7zip restic rsync shellcheck tree-sitter tree-sitter-cli tree-sitter-go tree-sitter-markdown \ tzdata zk \ git go \ neovim \ tmux \ mksh \ curl \ foot-extra-terminfo \ doas \ docker-cli \ tini RUN ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime && \ echo "Europe/Berlin" > /etc/timezone ENV TZ=Europe/Berlin # make mksh a valid login shell RUN echo "/bin/mksh" >> /etc/shells # create the user with mksh as its shell, no password (pubkey-only login) RUN adduser -D -s /bin/mksh "$USERNAME" # let the user talk to the docker socket mounted in at runtime without doas: # reuse whatever group already owns DOCKER_GID, or create "docker" if free RUN addgroup -g "$DOCKER_GID" docker 2>/dev/null; \ docker_group="$(getent group "$DOCKER_GID" | cut -d: -f1)" && \ addgroup "$USERNAME" "$docker_group" # passwordless doas for package installs etc. (no password auth is set up # for the user at all, so this must be nopass) RUN echo "permit nopass $USERNAME as root" > /etc/doas.conf && \ chown root:root /etc/doas.conf && \ chmod 0400 /etc/doas.conf # ssh access: authorized_keys sourced from GitHub, pubkey auth only RUN mkdir -p "/home/$USERNAME/.ssh" && \ curl -fsSL "https://github.com/${GITHUB_USER}.keys" -o "/home/$USERNAME/.ssh/authorized_keys" && \ test -s "/home/$USERNAME/.ssh/authorized_keys" && \ chown -R "$USERNAME:$USERNAME" "/home/$USERNAME/.ssh" && \ chmod 700 "/home/$USERNAME/.ssh" && \ chmod 600 "/home/$USERNAME/.ssh/authorized_keys" # clone the dotfiles repo and run its installer as the login user USER $USERNAME RUN git clone "$REPO_URL" "/home/$USERNAME/.config" && \ "/home/$USERNAME/.config/install.sh" USER root RUN mkdir -p /etc/dropbear # crontab spool lives on the home-data volume so it survives rebuilds; the # standard /var/spool/cron/crontabs path is symlinked to it so crond and # `crontab -e` keep working with no extra flags RUN mkdir -p "/home/$USERNAME/.crontabs" && \ touch "/home/$USERNAME/.crontabs/$USERNAME" && \ chown -R "$USERNAME:$USERNAME" "/home/$USERNAME/.crontabs" && \ chmod 700 "/home/$USERNAME/.crontabs" && \ chmod 600 "/home/$USERNAME/.crontabs/$USERNAME" && \ mkdir -p /var/spool/cron && \ ln -s "/home/$USERNAME/.crontabs" /var/spool/cron/crontabs RUN <<'EOF' cat > /usr/local/bin/entrypoint.sh #!/bin/sh set -e # host keys are generated on first start rather than at build time, so a # shared private key never ends up baked into the image. for t in rsa ed25519; do key="/etc/dropbear/dropbear_${t}_host_key" [ -f "$key" ] || dropbearkey -t "$t" -f "$key" done # busybox crond daemonizes itself by default (no -f), so this returns # immediately; tini (real PID 1) reaps it and any of its orphaned children crond -L /dev/stdout # -F: foreground, -E: log to stderr, -w: no root login, -s: no password auth exec dropbear -F -E -w -s -p 22 EOF RUN chmod +x /usr/local/bin/entrypoint.sh EXPOSE 22 ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]