Skip to content

Instantly share code, notes, and snippets.

@austinhyde
Last active September 5, 2022 03:23
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save austinhyde/2e39c01d6b0ebf4aef7409e129c47ea7 to your computer and use it in GitHub Desktop.
Save austinhyde/2e39c01d6b0ebf4aef7409e129c47ea7 to your computer and use it in GitHub Desktop.
Run Script, with dev docker image

One of the biggest barriers to starting in an unfamiliar repo is understanding how to start up the development environment, build the code, and other common tasks. Once a developer is established in a codebase, it's important to minimize development friction with common tasks.

The run script here is a very minimal script that makes it easy to see how to get started, what common tasks are available, and automates those tasks, no matter how trivial. Importantly for long-term maintainability, it allows the "repo maintainers" to change details about, say, starting a dev database, without most other engineers needing to care how that gets done (maybe we use docker-compose, maybe we use raw docker, maybe vagrant). And if they do care, they can just read the script or watch its output.

Importantly, the script is just plain old bash. Most developers are probably familiar with the syntax, and it's ubiquitous, meaning there's no dependencies to install and no esoteric shell-like syntaxes (like make) or libraries (like rake) to learn. So long as conventions around declaring commands, variables, and help text are conformed to, the file should be relatively self-documenting and easy to contribute to.

It's important to note that run scripts are not a build system. They're a task-automation system. You should still rely on make or similar tools to actually build your code.

The run-in-docker function provided here is a simple trick to automatically and transparently run the requested command inside a development docker image. This gives you the ability to create hermetic environments for running your commands, to drastically cut down on "works-on-my-machine" issues. You should probably use something like the golang container, or your own custom image, and make sure to mount in any build caches, dependency folders, ssh agents, etc, so that dockerized commands provide a near-native and transparent experience.

$ ./run help
Commands:
  some-task: some help text for the task
  some-dockerized-task: runs in docker if it's not already
  bash: opens bash in the docker container

Environment Variables:
  SOME_VARIABLE=foo	 some help text for the variable
  USE_DEV_DOCKER=yes	 whether or not to run enabled commmands in the docker dev environment

$ ./run help some-task
some-task is a function
some-task ()
{
  echo "hello $1"
}

$ ./run some-task world
hello world

$ ./run some-dockerized-task
Running './run some-dockerized-task' in development container
docker-desktop
root

$ USE_DEV_DOCKER=no ./run some-dockerized-task
mal.local
austin

$ ./run bash
Running './run bash' in development container
root@docker-desktop:/home/dev#

You can see a real example of this here: https://github.com/dbsteward/dbsteward-go/blob/master/run

#!/bin/bash
set -e -o pipefail
cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1
: "${SOME_VARIABLE:=foo}" # some help text for the variable
function some-task { #: some help text for the task
echo "hello $1"
}
function some-dockerized-task { #: runs in docker if it's not already
run-in-docker
hostname
whoami
}
function bash { #: opens bash in the docker container
run-in-docker -it
exec bash
}
: "${USE_DEV_DOCKER:=yes}" # whether or not to run enabled commmands in the docker dev environment
orig_args=("$@")
function run-in-docker {
if [[ $USE_DEV_DOCKER == "yes" ]]; then
script="$(basename "${BASH_SOURCE[0]}")"
echo "Running './$script ${orig_args[*]}' in development container"
docker_img="ubuntu:18.04" # or whatever you want
docker_args=(
--rm
--network host
-e USE_DEV_DOCKER=no
-w /home/dev
-v "$(pwd):/home/dev"
)
exec docker run "${docker_args[@]}" "$@" "$docker_img" "./$script" "${orig_args[@]}"
fi
}
if [[ $# -eq 0 || "$1" == "help" || "$1" == "--help" ]]; then
this_script="$(basename "$0")"
if [[ "$(type -t "$2")" == "function" ]]; then
type "$2"
else
echo "Commands:"
sed -En 's/^function (.*) \{ #/ \1/p' "$this_script"
echo
echo "Environment Variables:"
sed -En 's/^: "\$\{(.+):=(.+)\}"( #)?/ \1=\2\t/p' "$this_script"
fi
else
"$@"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment