Skip to content
AgileLogics
6 min read

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 RUN line runs in /bin/sh -c by default. SHELL switches to Bash with strict mode, so a failing command in a pipe fails the build.
  • Chaining with && and cleaning up in the same RUN keeps image layers small.
  • Minimal images (Alpine, distroless) may not include Bash at all — write POSIX sh or 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 — use set -euo pipefail (GitHub Actions' bash shell 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 -x would).

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.