Charging zsh as shell default for root or for other existing or new users in the containers.
1.4K
Ubuntu 24.04 LTS with zsh as the default interactive shell β Oh My Zsh, Powerlevel10k, eza, bat, git, passwordless sudo.
Base for others (e.g. cartagodocker/nodebunβ ). Root, uid 1000 (ubuntu), and later useradd -m users share .zshrc / .p10k.zsh / Oh My Zsh via /usr/share/globally.
The nodebun READMEβ uses the same section order. This page is the shell-base contract; NodeBun only documents runtimes it adds.
| π¦ GitHub | https://github.com/CartagoGit/DockerZshβ |
| π Docker Hub | https://hub.docker.com/r/cartagodocker/zshβ |
| π Changelog | CHANGELOG.mdβ |
Pin a version tag. No latest in production.
FROM cartagodocker/zsh:v2.0.0
| Piece | Notes | |
|---|---|---|
| π§ | OS | Ubuntu 24.04 LTS (Noble) |
| π | Interactive shell | zsh + Oh My Zsh + Powerlevel10k β CMD ["/usr/bin/zsh"] |
| π» | Other shells | bash and sh (dash) stay installed |
| π | Listing / pager | eza (ls), bat, GNU find + fd, rg, less, tree, nnn, ncdu, duf β Utilitiesβ |
| π§° | Daily CLI | Every extra binary + our helpers: Utilitiesβ |
| π | Catalogue | dockerzsh --help β same inventory in-container (Catalogue CLIβ ) |
| π | Network | curl, wget, git, openssh-client (no sshd), ca-certificates, ip/ss, socat, tcpdump |
| π | sudo | NOPASSWD for every uid (ALL ALL=(ALL:ALL) NOPASSWD:ALL) |
| π | Locale | LANG=C.UTF-8 Β· LC_ALL=C.UTF-8 |
| π§± | Build SHELL | ["/bin/sh", "-c"] β child RUN lines are POSIX, not zsh |
No openssh-server, no ENTRYPOINT: docker run / Compose command: is the process.
Not in this image (shell base): gcc/g++, python3, neovim, git-lfs, rclone, locales, man-db, nmap, file, 7zip, xmllint, git-extras, gpg/gnupg, expect/Tcl, dig/nslookup (bind9-dnsutils / libicu), iperf3, rlwrap, no sshd, no Docker (dockerd / docker CLI). Those β or a Docker client / commit signing β go in a child or on the host.
Container IP: ip -4 addr. DNS: /etc/resolv.conf. Resolve: getent hosts github.com (same as curl/git). Daily CLI: Utilitiesβ . Small extras (tcpdump, htpasswd) stay.
Extra CLIs we install (Ubuntu already has cp, mv, grep, awk, sed, find, top, β¦). Each table is name β docs. Image caveats: interactive zsh/bash/sh alias lsβeza and catβbat (--paging=never; rcat is GNU cat); GNU ls/find stay; Debian names batcat/fdfind; jsontools uses jq (no node/python/ruby); Tab stays native (no fzf-tab / zsh-autocomplete); no vscode/fasd; no sshd; no gcc. In-container: dockerzsh --help.
zsh is CMD. Interactive bash: eza/bat, zoxide, fzf keys, Tab, ble.sh, green/red β―. Interactive sh: eza + zoxide (no fzf keys). sh -c stays POSIX. Plugins: interactive zsh only (dockerzsh --plugins).
| Plugin | Docs |
|---|---|
git | gitβ |
extract / x | extractβ |
sudo | sudoβ |
jsontools | jsontoolsβ |
zsh-autosuggestions | zsh-autosuggestionsβ |
zsh-completions | zsh-completionsβ |
zsh-syntax-highlighting | zsh-syntax-highlightingβ |
zsh-history-substring-search | history-substring-searchβ |
you-should-use | you-should-useβ |
safe-paste | safe-pasteβ |
fancy-ctrl-z | fancy-ctrl-zβ |
dirhistory | dirhistoryβ |
zsh-bat | zsh-batβ |
| Tool | Docs |
|---|---|
nano | nanoβ |
vi | vim.tinyβ |
jq | jqβ |
jo | joβ |
sqlite3 | sqlite3β |
hexdump / xxd | hexdumpβ Β· xxdβ |
bc | bcβ |
column | columnβ |
| Tool | Docs |
|---|---|
unzip / zip | unzipβ Β· zipβ |
tar / gzip | tarβ Β· gzipβ |
xz | xzβ |
bzip2 | bzip2β |
zstd | zstdβ |
lz4 | lz4β |
pigz | pigzβ |
cpio | cpioβ |
cabextract | cabextractβ |
extract / x | extractβ |
uchardet | uchardetβ |
dos2unix | dos2unixβ |
| Tool | Docs |
|---|---|
curl / wget | curlβ Β· wgetβ |
ssh / scp / sftp | sshβ β SSH in this imageβ |
ping | pingβ |
tracepath | tracepathβ |
fping | fpingβ |
whois | whoisβ |
mtr | mtrβ |
traceroute | tracerouteβ |
rsync | rsyncβ |
nc | ncβ |
socat | socatβ |
ip / ss | ipβ Β· ssβ |
openssl | opensslβ |
tcpdump | tcpdumpβ |
htpasswd / ab | htpasswdβ Β· abβ |
No dig. IP: ip -4 addr. DNS: /etc/resolv.conf. Resolve: getent hosts github.com. ping/tcpdump often need NET_RAW / NET_ADMIN.
| Tool | Docs |
|---|---|
git / tig | gitβ Β· tigβ β SSHβ |
make | makeβ |
envsubst | envsubstβ |
htop | htopβ |
lsof | lsofβ |
killall | killallβ |
pv | pvβ |
sponge | spongeβ |
uuidgen | uuidgenβ |
getfacl / setfacl | getfaclβ |
getcap / setcap | setcapβ |
inotifywait | inotifywaitβ |
entr | entrβ |
strace | straceβ |
progress | progressβ |
tmux | tmuxβ |
keychain | keychainβ |
sudo | sudoβ β sudo in this imageβ |
| Tool | Docs |
|---|---|
fzf | fzfβ |
zoxide | zoxideβ |
colordiff | colordiffβ |
patch | patchβ |
fzf keys (Ctrl-R / Ctrl-T / Alt-C) in interactive zsh/bash only. Tab stays native. dash: no fzf keys. z is zoxide.
Not Ubuntu packages. ssh/scp/sftp/git on PATH wrap these β type the normal commands.
| Command | Docs |
|---|---|
add_text_to_zshrc | Scriptsβ |
add_text_to_p10k | Scriptsβ |
share_config_globally | Scriptsβ |
sudo-password | sudoβ |
sudo-nopasswd | sudoβ |
ssh-from-host | SSHβ |
git-from-host | SSHβ |
dockerzsh | Catalogueβ |
Uncompressed (disk / docker images) | Compressed (Hub pull / layers) | |
|---|---|---|
ubuntu:24.04 | ~78β―MB | ~28β―MB |
Hub zsh:v1.0.5 | ~205β―MB | ~76β―MB |
zsh:v2.0.0 | ~249β―MB (zsh-local:dev) | Hub gzip layers |
v1.0.5 was zsh + eza/bat/git. v2.0.0 is a daily-driver kit without ICU/Python/gnupg/7zip. Ubuntu git still pulls Perl. Not shipped (child if needed): xmllint, git-extras, file, 7zip, gpg, expect/Tcl, dig/nslookup, iperf3, rlwrap. rsync stays (C; no Python / rrsync). docker images is uncompressed; Hub pull is gzip. Oh My Zsh + p10k are in both 1.0.5 and 2.0.0.
Two different jobs. Mix them up and it looks like βeza is brokenβ.
Needs a TTY (-it or exec -it).
docker run --rm -it cartagodocker/zsh:v2.0.0
docker run --rm -it --user 1000:1000 -w /home/ubuntu cartagodocker/zsh:v2.0.0
docker exec -it <container> zsh
ls β eza. bat works. p10k is the same classic 2-line wizard as v1.0.5 (gitstatusd baked at build). Starting in / shows a lock (DIR_SHOW_WRITABLE) for uid 1000 β use -w /home/ubuntu or bind the project. Inside zsh: bash, sh, exit. Interactive bash/sh also alias lsβeza (zoxide; bash gets fzf keys) β no p10k. sh -c / Dockerfile RUN load no aliases.
The container stays up without a shell. Typical:
services:
app:
image: cartagodocker/zsh:v2.0.0
command: ["tail", "-f", "/dev/null"]
user: "1000:1000"
That process is tail, not a shell, and not a TTY. No prompt, no eza aliases, no p10k. Attach:
docker compose exec app zsh # prompt + eza + bat + p10k
docker compose exec app bash
docker run --rm image bash and docker run --rm image tail -f /dev/null work β no ENTRYPOINT swallows the command.
Default NOPASSWD (ALL ALL=(ALL:ALL) NOPASSWD:ALL). Global files (/usr/share/globally/.zshrc) are 644 root:root β uid 1000 cannot overwrite them. Use sudo or add_text_to_zshrc (NOPASSWD). Password is not baked; turn it on at runtime.
docker run --rm -it --user 1000:1000 cartagodocker/zsh:v2.0.0
# inside:
sudo -n id # default: no password
sudo-password # TTY prompt (hidden); afterwards sudo asks
sudo-password 'secret' # from arg (visible in `ps`)
SUDO_PASSWORD=secret sudo-password # from env
sudo-password 'new-secret' # change it (needs the *current* sudo password
# once one is already required)
sudo-nopasswd # back to NOPASSWD (needs the current password)
At start (first interactive/login shell via apply-sudo-password-on-boot.sh):
docker run --rm -it -e SUDO_PASSWORD=secret cartagodocker/zsh:v2.0.0
As uid 1000: NOPASSWD (default) escalates with sudo -n. Once a password is on, the helper uses sudo β type the current password, then it sets the new one (or drops it).
sudo-password writes /etc/container-sudo-password, switches sudoers NOPASSWD:ALL β ALL, and chpasswds every login user. Changing it overwrites that file and re-runs chpasswd. sudo -k drops the ticket cache. Compose user: "1000:1000" drops extra groups, so the rule is ALL, not %sudo.
The container only emits Unicode. The host terminal draws glyphs.
| What | Needs on the host | |
|---|---|---|
| π· | Powerlevel10k separators, git icons, eza file icons | A Nerd Fontβ (CaskaydiaCove / Cascadia Code NF) |
| π³ | Prompt whale (os_icon) | An emoji font β Segoe UI Emoji (Windows), Apple Color Emoji (macOS), Noto Color Emoji (Linux). Some Linux terminals have none β tofu. Same Unicode; the font is local. |
VS Code:
"terminal.integrated.fontFamily": "'Cascadia Code NF', 'CaskaydiaCove Nerd Font', Consolas, monospace"
Without a Nerd Font you get boxes on powerline / ls icons β not an image bug. No in-image fallback still looks like p10k.
All write /usr/share/globally/... (every user). sudo if needed.
add_text_to_zshrcadd_text_to_zshrc "alias hello='echo hi'"
add_text_to_zshrc "alias hello='echo hi'" --prepend
add_text_to_zshrc "$(printf '%s\n' \
'alias hello="echo hi"' \
'alias bye="echo bye"')"
FROM cartagodocker/zsh:v2.0.0
RUN add_text_to_zshrc "$(printf '%s\n' \
'alias hello="echo hi"')"
add_text_to_p10kSame CLI, writes .p10k.zsh.
add_text_to_p10k "typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose"
share_config_globallyMove a path from a user home (default /root) to /usr/share/<name> and symlink it for /root, /home/*, /etc/skel.
share_config_globally .local/share/fnm
share_config_globally .oh-my-zsh --to globally/.oh-my-zsh --permissions 755
--permissions default 755. Use 777 only when every uid must write the shared tree (package caches).
openssh-client is installed. Bind host ~/.ssh and type ssh / git. Root or any uid β one volume.
services:
dev:
image: cartagodocker/zsh:v2.0.0
volumes:
- ~/.ssh:/${USER}/.ssh:ro
- ~/.gitconfig:/${USER}/.gitconfig:ro
docker run --rm -it \
-v "$HOME/.ssh:/$USER/.ssh:ro" \
-v "$HOME/.gitconfig:/$USER/.gitconfig:ro" \
cartagodocker/zsh:v2.0.0
# inside: ssh [email protected]
# git commit # author = host user.name, not ubuntu@id
${USER} is the host name. Docker needs an absolute target (/${USER}/.ssh β /cartago/.ssh), not ${USER}/.ssh (not $HOME in the container). The client finds any bind ending in .ssh and copies keys to a uid-owned 700 dir (OpenSSH rejects 644/777 and cannot write known_hosts on :ro). Root or any uid.
Git author is not the SSH key. git pull/push use the key; git commit needs user.name + user.email. Bind host ~/.gitconfig (or GIT_AUTHOR_NAME + GIT_AUTHOR_EMAIL). Any absolute path ending in .gitconfig works. Without it, git commit says identity is required and exits 1 β no invented ubuntu@<container>.
Missing keys: ssh/scp/sftp print keys are required (bind example; another directory is fine), then OpenSSH still runs.
known_hosts is optional. Binding ~/.ssh brings the host file if present.
| Layer | Where | What |
|---|---|---|
| Official (baked) | /usr/share/ssh/known_hosts and /etc/ssh/ssh_known_hosts | GitHub / GitLab keys. GlobalKnownHostsFile. Same content twice so /usr/bin/ssh still trusts them if the wrapper is bypassed. |
| Your host file (if present) | copied / merged into /tmp/container-ssh-<uid>/known_hosts | Used as-is. New hosts (accept-new) append there, never on the :ro volume. Re-runs merge new lines and keep container-learned hosts. |
Without a host known_hosts, GitHub / GitLab still work from the baked file. Other hosts are learned on first connect and stay in /tmp for this container.
Agent (key never enters the container) still works if you already use one:
docker run --rm -it \
-v "$SSH_AUTH_SOCK":/ssh-agent \
-e SSH_AUTH_SOCK=/ssh-agent \
cartagodocker/zsh:v2.0.0
Not SSH-into-the-container. Attach: docker exec -it β¦ zsh.
dockerzsh)In-container dockerzsh catalogues every tool and helper β not the host docker CLI.
docker run --rm cartagodocker/zsh:v2.0.0 dockerzsh --help
docker compose exec app dockerzsh --help
The full dump is long on purpose. Filter by section:
dockerzsh --sections | -s # list ids
dockerzsh --shells # one section
dockerzsh --plugins # Oh My Zsh plugins (interactive zsh)
dockerzsh shells # same (flag or bare id)
dockerzsh --section shells # same
dockerzsh shells network # several sections
dockerzsh --version | -v # image identity (ZSH_IMAGE_VERSION)
dockerzsh --list | -l # tool names
dockerzsh --list --version | -l -v # probe every catalogue tool now
dockerzsh shells --version | shells -v
dockerzsh eza --version | eza -v
| Id | Section (every tool β same list as Utilitiesβ ) |
|---|---|
about | What this image is (CMD, no ENTRYPOINT, TTY vs keep-alive) |
usage | How to invoke dockerzsh |
shells | zsh, bash, sh |
plugins | Oh My Zsh plugins (interactive zsh only) β Utilitiesβ |
listing | eza, bat, fd, rg, less, tree, nnn, ncdu, duf |
edit | nano, vi, jq, jo, sqlite3, hexdump/xxd, bc, column |
archives | unzip/zip, tar/gzip, xz, bzip2, zstd, lz4, pigz, cpio, cabextract, extract/x, uchardet, dos2unix |
network | curl/wget, ssh/scp/sftp, ping, tracepath, fping, whois, mtr, traceroute, rsync, nc, socat, ip/ss, openssl, tcpdump, htpasswd/ab |
system | git/tig, make, envsubst, htop, lsof, killall, pv, sponge, uuidgen, getfacl/setfacl, getcap/setcap, inotifywait, entr, strace, progress, tmux, keychain, sudo |
extras | fzf, zoxide, colordiff, patch |
helpers | add_text_to_zshrc, add_text_to_p10k, share_config_globally, sudo-password, sudo-nopasswd, git-from-host, dockerzsh |
fonts | Host fonts + what is not in this image |
Unknown ids exit 2 (--sections). --shells and shells are the same id (listing also accepts --ls).
docker build --build-arg VERSION=2.0.0 -t zsh-local:dev -f ./Dockerfile ./
GitHub Actions (secrets DOCKERHUB_USERNAME, DOCKERHUB_PASSWORD; variable DOCKERHUB_REPO):
| Trigger | What happens |
|---|---|
Git tag v* | Build + push cartagodocker/zsh:<tag> if new (skip if it exists). Create/replace a GitHub Release. No Hub :latest. β docker-hub-update.ymlβ |
Push to main that changes README.md | Hub long description (full_description β€ ~25β―000 chars) β update-dockerhub-description.ymlβ |
See CHANGELOG.mdβ .
Content type
Image
Digest
sha256:e2219eb97β¦
Size
92.9 MB
Last updated
26 days ago
docker pull cartagodocker/zsh:v2.0.0