Skip to content

Instantly share code, notes, and snippets.

@metafeather
Last active April 5, 2022 09:19
Show Gist options
  • Save metafeather/44461f814845eed3e0e6d567735209c1 to your computer and use it in GitHub Desktop.
Save metafeather/44461f814845eed3e0e6d567735209c1 to your computer and use it in GitHub Desktop.
Personal dev container
# Global args
ARG DEBIAN_VERSION="bullseye-slim"
ARG GOLANG_VERSION="1.17"
ARG NODE_VERSION="16"
ARG PYTHON_VERSION="3.10"
ARG RUBY_VERSION="3.1.0"
############################
# Base images
############################
FROM scratch AS base
FROM debian:${DEBIAN_VERSION} AS hostos
# ref: https://github.com/GoogleContainerTools/distroless
# FROM gcr.io/distroless/nodejs
FROM node:${NODE_VERSION}-bullseye-slim AS node
FROM python:${PYTHON_VERSION}-slim-bullseye AS python
FROM golang:${GOLANG_VERSION}-bullseye AS golang
FROM ruby:${RUBY_VERSION}-slim-bullseye AS ruby
############################
# Runtime IDE
############################
# ref: https://github.com/ls12styler/ide/blob/master/Dockerfile
# ref: https://github.com/JAremko/drop-in/blob/master/Dockerfile
# ref: https://medium.com/@ls12styler/docker-as-an-integrated-development-environment-95bc9b01d2c1
# ref: https://medium.com/rate-engineering/using-docker-containers-as-development-machines-4de8199fc662
# ref: https://gitlab.com/ESC/containers/-/blob/master/Docker/utilities/dsh/dsh
# ref: https://jtreminio.com/blog/running-docker-containers-as-current-host-user/#ok-so-what-actually-works
FROM hostos AS runtime
USER root
WORKDIR /tmp
# Change to force rebuild of all layers
RUN echo "runtime: 2022-03-21.1"
# Set Timezone for tzdata
ENV TZ=Europe/London
# Get security updates
RUN apt-get update --yes
RUN apt-get upgrade --yes
# install - packages.txt
COPY packages.txt /tmp/packages.txt
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update --yes \
&& apt-get install --yes --no-install-recommends $(grep -o ^[^#]* /tmp/packages.txt) \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Extra distributions
RUN curl -fsSL 'https://download.docker.com/linux/debian/gpg' | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
RUN echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
RUN apt-get update --yes
RUN apt-get install -y --no-install-recommends \
docker-ce-cli
# Cleanup apt lists
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Bugfixes
# ref: https://github.com/sudo-project/sudo/issues/42
RUN echo "Set disable_coredump false" >> /etc/sudo.conf
############################
# Developer runtime
############################
FROM runtime AS developer
USER root
# ref: https://vsupalov.com/docker-arg-env-variable-guide/
# Build args - defaults based on Amazon EC2
ARG HOST_OS="Linux"
ARG HOST_HOSTNAME="user"
ARG HOST_USER="user"
ARG HOST_USER_UID=1000
ARG HOST_USER_GID=${HOST_USER_UID}
ARG HOST_HOME="/home/${HOST_USER}"
ARG HOST_COLOR=""
ARG PROJECT_NAME="user"
ARG PROJECT_HOME="${HOST_HOME}/opt/${PROJECT_NAME}"
ARG PROJECT_COLOR=""
# Share build args in env
ENV HOST_OS ${HOST_OS}
ENV HOST_HOSTNAME ${HOST_HOSTNAME}
ENV HOST_USER ${HOST_USER}
ENV HOST_USER_UID ${HOST_USER_UID}
ENV HOST_USER_GID ${HOST_USER_GID}
ENV HOST_HOME ${HOST_HOME}
ENV HOST_COLOR ${HOST_COLOR}
ENV PROJECT_NAME ${PROJECT_NAME}
ENV PROJECT_COLOR ${PROJECT_COLOR}
# Duplicates host user as UID/GID=1000 and adds to groups
# ref: https://wiki.alpinelinux.org/wiki/Setting_up_a_new_user
# ref: docker run alpine sh -c 'apk add shadow sudo && useradd'
RUN groupadd \
--force \
--gid ${HOST_USER_GID} \
--non-unique \
${HOST_USER}
# Mount /home/code/user as $HOME for mapped host user
RUN useradd \
--comment 'Added by Dockerfile' \
--home-dir ${PROJECT_HOME} \
--gid ${HOST_USER_GID} \
--create-home \
--non-unique \
--shell /bin/zsh \
--uid ${HOST_USER_UID} \
${HOST_USER}
RUN chown -R ${HOST_USER_UID}:${HOST_USER_GID} ${PROJECT_HOME}
# Passwordless sudo
RUN echo ${HOST_USER} ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${HOST_USER} \
&& chmod 0440 /etc/sudoers.d/${HOST_USER}
# give permission to access host docker via bind mount /var/run/docker.sock
RUN groupadd docker
RUN addgroup ${HOST_USER} docker
############################
# Build/download binary utils
############################
# ref: https://weberc2.bitbucket.io/posts/golang-docker-scratch-app.html
FROM golang AS binaries
USER root
# Set Timezone for tzdata
ENV TZ=Europe/London
# Get security updates
RUN apt-get update --yes
RUN apt-get upgrade --yes
# Install git + SSL CA certificates.
# Git is required for fetching the dependencies.
# CA-certificates is required to call HTTPS endpoints.
RUN apt-get install --yes --no-install-recommends \
git ca-certificates tzdata \
curl
RUN update-ca-certificates
WORKDIR /opt/bin
ENV PATH="/opt/bin:${PATH}"
# Debug Docker build env
RUN go env
# Build binaries with CGO_ENABLED=0 so we use the pure-Go implementations for
# things like DNS resolution (so we don't depend on system libraries)
#RUN go get github.com/ochinchina/supervisord
#RUN env CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -a -installsuffix cgo -o ./bin/supervisord github.com/ochinchina/supervisord
# TODO: build tmux
############################
# User image
############################
FROM developer as user
# User can install binaries
RUN chgrp ${HOST_USER} /usr/local/bin && chmod g+w /usr/local/bin
# Run as nonroot user to match the host
ENV USER ${HOST_USER}
ENV HOME ${PROJECT_HOME}
USER ${USER}
WORKDIR ${HOME}
# Uses SSH auth socket location set in .bash_aliases on host SSH login
# and mounted into Docker so agent forwarding works with tmux and in containers
# ref: https://blog.testdouble.com/posts/2016-11-18-reconciling-tmux-and-ssh-agent-forwarding/
ENV SSH_AUTH_SOCK "/tmp/tmux_ssh-agent.sock"
# Copy standalone binaries
COPY --chown=${USER} --from=binaries /opt/bin/ /opt/bin/
ENV PATH="/opt/bin:${PATH}"
RUN mkdir -p ~/.cache/bin
ENV PATH=~/.cache/bin:${PATH}
# Create build time dirs and symlinks
# Can be overridden by volume mounts
RUN mkdir -p \
/tmp/.vscode-server
RUN ln -sf /tmp/.vscode-server ~/
# Gather expected build time configs
COPY --chown=${USER} entrypoint*.sh ./
RUN chmod +x entrypoint*.sh
# Run entrypoint, can be overridden
WORKDIR ${HOME}
ENTRYPOINT ["./entrypoint.sh"]
#!/bin/sh
cd ${HOME}
echo "Docker image ready for shell access."
# own $HOME mounts
sudo chown ${USER}: ${HOME}/.*
sudo chown ${USER}: ${HOME}/*
# give permission to access host docker via bind mount /var/run/docker.sock
# sudo chown root:docker /var/run/docker.sock
sudo touch /var/run/docker.sock
# create group matching host os docker gid
sudo groupadd --system --force --gid $(stat -c %g /var/run/docker.sock) --non-unique docker_host
sudo addgroup $USER docker_host
sudo chmod g+w /var/run/docker.sock
# ensure new group is used but not primary and run keepalive
# commands in subshells with expected groups
# ref: https://unix.stackexchange.com/questions/18897/problem-while-running-newgrp-command-in-script
newgrp docker_host <<SUB1
newgrp $USER <<SUB2
# Enable direnv
direnv allow
export PROJECT_NAME="${PROJECT_NAME:-'user'}"
# Keepalive via tmux session
# while true
# do
# if [[ "$(tmux ls 2>$1)" != *""${PROJECT_NAME}""* ]]; then
# tmux new \
# -s "${PROJECT_NAME}" \
# -c "${HOME}" \
# -n "~" \
# -d
# echo "Started missing '${PROJECT_NAME}' tmux session"
# fi
# sleep 5
# done
# Keepalive
while :; # Run an endless loop,
do :; # of do nothing,
done & # as background task.
kill -STOP $! # Stop the background task.
wait $! # Wait forever, because background task process has been stopped.
SUB2
SUB1
# TODO: make devcontainer
# ref: https://code.visualstudio.com/docs/remote/create-dev-container
# ref: https://benmatselby.dev/post/vscode-dev-containers/
# see also: https://code.visualstudio.com/docs/remote/devcontainerjson-reference
.DEFAULT_GOAL: help
ORG ?= $(shell hostname -s | tr A-Z a-z)
NAME ?= user.$(ORG)
VERSION ?= $(NAME):$(shell git rev-parse HEAD)
LATEST ?= $(NAME):latest
############################
# Utils
############################
# callable with args, e.g. $(call help, Some text)
log = @(printf "\033[32m>> $1\n\033[39m")
# Dynamic help generated from usage vars
.PHONY: help
help: usage := Show usage messages
help: usage
help:
@grep -E '^[a-zA-Z_-]+:.*?usage := .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?usage := "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: usage
usage:
$(call log, $(usage))
############################
# Targets
############################
.PHONY: down
down: usage := Stop the docker image
down: usage
down:
@docker rm -f $(NAME) || true
EXTRA_DOCKER_RUN_ARGS ?= -p 9000-9019:9000-9019
# --mount type=bind,src=$(shell readlink -f "/tmp/tmux_ssh-agent.sock"),dst=/tmp/tmux_ssh-agent.sock
.PHONY: up
up: usage := Start the docker image
up: usage
up: HOST_HOSTNAME ?= $(NAME).docker
up: HOST_HOME ?= $(HOME)
up: HOST_COLOR ?= cdf632
up: PROJECT_COLOR ?= f8961e
up:
@docker run -dt --restart unless-stopped \
--add-host=host.docker.internal:host-gateway \
--name $(NAME) \
-h $(HOST_HOSTNAME) \
--log-opt max-size=10m --log-opt max-file=5 \
--env HOST_COLOR=$(HOST_COLOR) \
--env PROJECT_COLOR=$(PROJECT_COLOR) \
--mount source=$(NAME).vscode-server,target=/tmp/.vscode-server \
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
--mount type=bind,src=$(HOST_HOME)/opt,dst=$(HOST_HOME)/opt \
--mount type=bind,src=$(HOST_HOME)/.ssh,dst=$(HOST_HOME)/opt/user/.ssh \
--env SSH_AUTH_SOCK=$(HOST_HOME)/opt/user/.ssh/agent.sock \
$(EXTRA_DOCKER_RUN_ARGS) \
$(LATEST)
.PHONY: logs
logs: usage := View realtime logs of the running docker image
logs: usage
logs:
@docker logs -f $(NAME)
.PHONY: shell
shell: usage := Attach a shell to the running docker image
shell: usage
shell:
@docker exec -it $(NAME) /bin/zsh
### Deployments
.PHONY: build
build: usage := Build the docker image
build: usage
build: HOST_OS ?= $(shell uname -s)
build: HOST_HOSTNAME ?= $(NAME).docker
build: HOST_USER ?= $(USER)
build: HOST_USER_UID ?= $(shell id -u)
build: HOST_USER_GID ?= $(shell id -g)
build: HOST_HOME ?= $(HOME)
build:
@DOCKER_BUILDKIT=1 docker build --rm -f Dockerfile \
--build-arg HOST_OS=$(HOST_OS) \
--build-arg HOST_HOSTNAME=$(HOST_HOSTNAME) \
--build-arg HOST_USER=$(HOST_USER) \
--build-arg HOST_USER_UID=$(HOST_USER_UID) \
--build-arg HOST_HOME=$(HOST_HOME) \
--target user \
-t $(LATEST) .
.PHONY: user
user: usage := User: default
user: build down up
# Allow adding repos
apt-transport-https
ca-certificates
debian-keyring
debian-archive-keyring
gnupg2
software-properties-common
# Basic utils
mandoc
mime-support
util-linux
xdg-utils
procps
# Common build libs
bzip2
clang
gcc
libssl-dev
make
zlib1g-dev
# Common network access
netcat # Used to connect to host vscode
openssh-client
socat
# Common tools
bash # for tmux + tpm to work
bash-completion
curl
git
less
rsync
sed
sudo
tmux
unzip
wget
zsh
# User tools
hstr
htop
postgresql-client
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment