Skip to content

Instantly share code, notes, and snippets.

@duruyao
Last active January 5, 2024 02:30
Show Gist options
  • Save duruyao/cd662e2783f64e68e6f33c1b2a3184f8 to your computer and use it in GitHub Desktop.
Save duruyao/cd662e2783f64e68e6f33c1b2a3184f8 to your computer and use it in GitHub Desktop.
Docker container running tool: use the host's user and configuration within the container and cache the software packages to the host.
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
# usage: bash docker-run.sh [OPTIONS] IMAGE [COMMAND] [ARG...]
function get_valid_port() {
local random_port
random_port="$((RANDOM % (49151 - 1024 + 1) + 1024))"
if [ -n "$(command -v netstat)" ]; then
while netstat -tuln | grep -q ":${random_port}"; do
random_port="$((RANDOM % (49151 - 1024 + 1) + 1024))"
done
fi
echo "${random_port}"
}
OPTIONS=()
IMAGE=()
COMMAND_ARGS=()
while ((${#})); do
repository_tag="$(docker inspect --format "{{index .RepoTags 0}}" "${1}" 2>/dev/null || true)"
if [ -z "${repository_tag}" ] || [[ "${1}" =~ ^[0-9]+$ ]] || [ "${1}" == "-h" ] || [ "${1}" == "--help" ]; then
OPTIONS+=("${1}")
shift 1
else
IMAGE+=("${repository_tag}")
shift 1
COMMAND_ARGS+=("${@}")
break
fi
done
if [ -n "${IMAGE[*]+"NOT EMPTY"}" ]; then
CONTAINER_HOME="/${USER}"
HOST_CACHE_DIR="${HOME}/.docker_cache/$(docker images --format "{{.ID}}" "${IMAGE[*]}")"
mkdir -p "${HOST_CACHE_DIR}/login"
mkdir -p "${HOST_CACHE_DIR}/${CONTAINER_HOME}/.local"
if [ -d "${HOME}/.ssh" ]; then cp -rf "${HOME}/.ssh" "${HOST_CACHE_DIR}/${CONTAINER_HOME}/"; fi
if [ -f "${HOME}/.gitconfig" ]; then cp -f "${HOME}/.gitconfig" "${HOST_CACHE_DIR}/${CONTAINER_HOME}/"; fi
mkdir -p "${HOST_CACHE_DIR}/${CONTAINER_HOME}/.go/bin"
mkdir -p "${HOST_CACHE_DIR}/${CONTAINER_HOME}/.go/pkg"
mkdir -p "${HOST_CACHE_DIR}/${CONTAINER_HOME}/.go/src"
echo "
#!/usr/bin/env bash
set -e
COMMAND_ARGS=(\"\${@}\")
mkdir -p /login
{
set -x
for shell in zsh fish bash sh; do
[ -n \"\$(command -v \"\${shell}\")\" ] && custom_shell=\"\$(command -v \"\${shell}\")\" && break
done
useradd -ms \"\${custom_shell-\"/bin/sh\"}\" -d \"\${THIS_BUILD_HOME}\" \"\${THIS_BUILD_USER}\"
usermod -aG sudo \"\${THIS_BUILD_USER}\"
echo \"\${THIS_BUILD_USER}\":\"\${THIS_BUILD_USER}\" | chpasswd
usermod -u \"\${THIS_BUILD_UID}\" \"\${THIS_BUILD_USER}\"
groupmod -g \"\${THIS_BUILD_GID}\" \"\${THIS_BUILD_GROUP}\" || true
for config in /root/.vim /root/.vimrc /root/.bashrc /root/.zshrc /root/.zprofile /root/.oh-my-zsh /root/.tmux.conf; do
[ -e \"\${config}\" ] && cp -rf \"\${config}\" \"\${THIS_BUILD_HOME}\"/
done
chown -R \"\${THIS_BUILD_USER}\":\"\${THIS_BUILD_USER}\" \"\${THIS_BUILD_HOME}\"
service ssh start || true
set +x
} 1>/login/login.log 2>&1
if [ -z \"\$(command -v sudo)\" ]; then
su --login \"\${THIS_BUILD_USER}\" --pty --command \"export HOME=\${THIS_BUILD_HOME} PYTHONPATH=\${PYTHONPATH} PATH=\${THIS_BUILD_HOME}/.local/bin:\${GOPATH}/bin:\${PATH}; \${COMMAND_ARGS[*]}\"
else
sudo --user \"#\${THIS_BUILD_UID}\" --preserve-env HOME=\"\${THIS_BUILD_HOME}\" PYTHONPATH=\"\${PYTHONPATH}\" PATH=\"\${THIS_BUILD_HOME}/.local/bin:\${GOPATH}/bin:\${PATH}\" -- \"\${COMMAND_ARGS[@]}\"
fi
" >"${HOST_CACHE_DIR}/login/with-the-same-user.sh"
DOCKER_ENV+=(
--env THIS_BUILD_USER="$(id -u -n)"
--env THIS_BUILD_UID="$(id -u)"
--env THIS_BUILD_GROUP="$(id -g -n)"
--env THIS_BUILD_GID="$(id -g)"
--env THIS_BUILD_HOME="${CONTAINER_HOME}"
--env https_proxy="http://10.0.13.122:3128"
--env HTTPS_PROXY="http://10.0.13.122:3128"
--env http_proxy="http://10.0.13.122:3128"
--env HTTP_PROXY="http://10.0.13.122:3128"
--env all_proxy="socks5://10.0.13.122:3128"
--env ALL_PROXY="socks5://10.0.13.122:3128"
--env GOPATH="${CONTAINER_HOME}/.go"
)
DOCKER_MOUNT+=(
--volume "${HOST_CACHE_DIR}/login:/login"
--volume "${HOST_CACHE_DIR}/${CONTAINER_HOME}/.local:${CONTAINER_HOME}/.local"
--volume "${HOST_CACHE_DIR}/${CONTAINER_HOME}/.ssh:${CONTAINER_HOME}/.ssh"
--volume "${HOST_CACHE_DIR}/${CONTAINER_HOME}/.gitconfig:${CONTAINER_HOME}/.gitconfig"
--volume "${HOST_CACHE_DIR}/${CONTAINER_HOME}/.go:${CONTAINER_HOME}/.go"
)
DOCKER_PUBLISH+=(
--publish "$(get_valid_port):22"
)
DOCKER_LOGIN+=(
bash --login /login/with-the-same-user.sh
)
fi
DOCKER_CMD=(docker run
${OPTIONS[@]+"${OPTIONS[@]}"}
${DOCKER_ENV[@]+"${DOCKER_ENV[@]}"}
${DOCKER_MOUNT[@]+"${DOCKER_MOUNT[@]}"}
${DOCKER_PUBLISH[@]+"${DOCKER_PUBLISH[@]}"}
${IMAGE[@]+"${IMAGE[@]}"}
${DOCKER_LOGIN[@]+"${DOCKER_LOGIN[@]}"}
${COMMAND_ARGS[@]+"${COMMAND_ARGS[@]}"}
)
echo -e "\033[7m"
echo -e ${DOCKER_CMD[@]+"${DOCKER_CMD[@]}"}
echo -e "\033[0m"
${DOCKER_CMD[@]+"${DOCKER_CMD[@]}"}
@duruyao
Copy link
Author

duruyao commented Jan 4, 2024

Features

  • Cache the software packages installed in the Docker container to an external host.
  • Use users and configurations of external host inside Docker containers.
  • Log into Docker containers via SSH (environment variables missing).
  • Compatible with Linux, macOS and Windows WSL.

Examples

Start a Docker container with the command bash docker-run.sh [OPTIONS] IMAGE [COMMAND] [ARG...], which is similar to the original Docker command, just replace docker run or docker container run with bash docker-run.sh.

In the following example, the script docker-run.sh outputs and executes a real Docker command.

bash docker-run.sh -it --name mc.$RANDOM --rm -v $(realpath $PWD):$PWD -w $PWD duruyao/vimicro-mc:cpu zsh
docker run -it --name mc.29504 --rm -v /home/Projects/hello:/home/Projects/hello -w /home/Projects/hello \
    --env THIS_BUILD_USER=username \
    --env THIS_BUILD_UID=1000 \
    --env THIS_BUILD_GROUP=username \
    --env THIS_BUILD_GID=1000 \
    --env THIS_BUILD_HOME=/username \
    --env https_proxy=http://10.0.13.122:3128 \
    --env HTTPS_PROXY=http://10.0.13.122:3128 \
    --env http_proxy=http://10.0.13.122:3128 \
    --env HTTP_PROXY=http://10.0.13.122:3128 \
    --env all_proxy=socks5://10.0.13.122:3128 \
    --env ALL_PROXY=socks5://10.0.13.122:3128 \
    --env GOPATH=/username/.go \
    --volume /home/username/.docker_cache/fe981bdcb5d3/login:/login \
    --volume /home/username/.docker_cache/fe981bdcb5d3/username/.local:/username/.local \
    --volume /home/username/.docker_cache/fe981bdcb5d3/username/.ssh:/username/.ssh \
    --volume /home/username/.docker_cache/fe981bdcb5d3/username/.gitconfig:/username/.gitconfig \
    --volume /home/username/.docker_cache/fe981bdcb5d3/username/.go:/username/.go \
    --publish 31320:22 \
    duruyao/vimicro-mc:gpu bash --login /login/with-the-same-user.sh zsh

Log into the above running container with the following Docker command.

docker exec -it --user $USER mc.29504 zsh

Log into the above running container with the following SSH command, if you don't mind the environment variables missing.

Your password in the container is your username.

ssh $USER@localhost -p 31320

Cache Python packages installed by command pip install requires the option --user. The following is an example.

pip install --user you-get

Since GOPATH has been preset, Go packages installed by command go get can be cached automatically. The following is an example.

go get -u github.com/julienschmidt/httprouter

Cache packages installed from source requires to set install prefix as ~/.local in a container. The following is an example.

cmake -S . \
  -B build \
  -G "Unix Makefiles" \
  -D CMAKE_BUILD_TYPE=Release \
  -D CMAKE_INSTALL_PREFIX="~/.local"
cmake --build build --target all -- -j $(($(nproc) / 2))
cmake --build build --target install

Remarks

View cached files on the external host by running the following command.

tree ~/.docker_cache -a -L 7
/home/username/.docker_cache
└── fe981bdcb5d3
    ├── username
    │   ├── .gitconfig
    │   ├── .go
    │   │   ├── bin
    │   │   ├── pkg
    │   │   │   ├── mod
    │   │   │   │   ├── cache
    │   │   │   │   │   ├── download
    │   │   │   │   │   └── lock
    │   │   │   │   └── github.com
    │   │   │   │       └── julienschmidt
    │   │   │   └── sumdb
    │   │   │       └── sum.golang.org
    │   │   │           └── latest
    │   │   └── src
    │   ├── .local
    │   │   ├── bin
    │   │   │   └── you-get
    │   │   └── lib
    │   │       └── python3.9
    │   │           └── site-packages
    │   │               ├── you_get
    │   │               └── you_get-0.4.1650.dist-info
    │   └── .ssh
    │       ├── authorized_keys
    │       ├── config
    │       ├── id_rsa
    │       ├── id_rsa.pub
    │       └── known_hosts
    └── login
        ├── login.log
        └── with-the-same-user.sh

Please modify this script docker-run.sh according to your needs, such as the proxy server part and more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment