Shell in Containers and CI
Prerequisites: Strict mode: set -euo pipefail, Portability and POSIX sh
Dockerfiles and CI pipelines are full of shell commands. The same habits — strict error handling, quoting, portability — apply there too.
Dockerfiles
FROM debian:bookworm-slim
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["serve"]
- Each
RUNline runs in/bin/sh -cby default.SHELLswitches to Bash with strict mode, so a failing command in a pipe fails the build. - Chaining with
&&and cleaning up in the sameRUNkeeps image layers small. - Minimal images (Alpine, distroless) may not include Bash at all — write
POSIX
shor install Bash explicitly.
Entrypoint scripts
# illustrative
#!/bin/sh
set -eu
# Wait for the database, with a timeout
tries=0
until nc -z "${DB_HOST:?}" "${DB_PORT:-5432}"; do
tries=$((tries + 1))
[ "$tries" -ge 30 ] && { echo "database not reachable" >&2; exit 1; }
sleep 1
done
exec "$@"
exec "$@" replaces the script with the container's main command, so that
process receives signals (like SIGTERM on shutdown) directly and stops
cleanly.
CI pipelines
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Lint shell scripts
run: shellcheck scripts/*.sh
- name: Run tests
run: |
set -euo pipefail
./scripts/test.sh
- Multi-line
run:blocks are shell scripts — useset -euo pipefail(GitHub Actions'bashshell already sets-eo pipefail). - Keep real logic in versioned script files (
scripts/test.sh) rather than long inline YAML, so it can be run and tested locally too. - Pass secrets through the CI system's secret variables, and avoid
printing them (
set -xwould).
Makefiles
Many projects use a Makefile as a menu of common tasks (make test,
make deploy). Each recipe line runs in its own shell, so cd doesn't
carry over between lines — chain commands with && on one line.