Skip to content

Instantly share code, notes, and snippets.

@usrbinkat
Last active June 4, 2025 19:38
Show Gist options
  • Save usrbinkat/1bf128b0f998518b335b371365bc4fb8 to your computer and use it in GitHub Desktop.
Save usrbinkat/1bf128b0f998518b335b371365bc4fb8 to your computer and use it in GitHub Desktop.
Testing ContainerCraft Dockerfile Style Guide

ContainerCraft Dockerfile Style Guide

Version: 1.0.0
Last Updated: December 2024
Maintainer: ContainerCraft

Table of Contents

  1. Header Documentation
  2. Base Image and Metadata
  3. Environment Variables
  4. Package Installation
  5. Tool Installation Pattern
  6. User and Permission Management
  7. Directory Structure
  8. Cleanup and Optimization
  9. Multi-Stage Builds
  10. Formatting Standards
  11. Testing and Validation
  12. Image Metadata

1. Header Documentation

Every Dockerfile MUST begin with a standardized header comment block:

###############################################################################
# Use:
# - docker build --progress plain --tag ghcr.io/[org]/[image]:[tag] -f docker/[variant]/Dockerfile ./docker
# - docker run --rm -d --name [container] --hostname [hostname] ghcr.io/[org]/[image]:[tag]
###############################################################################

Requirements:

  • Use 79 # characters for separator lines
  • Include both docker build and docker run example commands
  • Use --progress plain for build visibility
  • Include the full registry path in tags
  • Specify the Dockerfile path with -f flag
  • Include essential run flags (--rm, -d, --name, --hostname)

2. Base Image and Metadata

Immediately after the header, define the base image and initial metadata:

FROM ghcr.io/containercraft/devcontainer:base
LABEL tag="variant-name"
ENV DEVCONTAINER="variant-name"

Requirements:

  • Use specific, versioned base images when possible
  • Set both LABEL tag and ENV DEVCONTAINER to the same variant name
  • Place these declarations immediately after the FROM statement

3. Environment Variables

3.1 Core Environment Setup

Define environment variables in logical groups with clear purposes:

# Disable LC_ALL for Nix compatibility
ENV LC_ALL=""
# Disable timezone prompts
ENV TZ=UTC
# Set TERM to linux to avoid prompts
ENV TERM=linux
# Disable package manager prompts
ENV DEBIAN_FRONTEND=noninteractive

3.2 Path Configuration

# Add go and nix to path
ENV PATH="/home/ubuntu/.krew/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/nix/var/nix/profiles/default/bin"

3.3 Common Build Functions

Define reusable command patterns as environment variables:

# Common Dockerfile Container Build Functions
ENV apt_update="sudo apt-get update"
ENV apt_install="TERM=linux DEBIAN_FRONTEND=noninteractive sudo apt-get install -q --yes --purge --assume-yes --auto-remove --allow-downgrades -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
ENV apt_clean="sudo apt-get clean && sudo apt-get autoremove -y && sudo apt-get purge -y --auto-remove"
ENV curl="/usr/bin/curl --silent --show-error --tlsv1.2 --location"
ENV curl_github="/usr/bin/curl --silent --show-error --tlsv1.2 --request GET --url"
ENV dir_clean="\
  sudo rm -rf \
  /var/lib/{apt,cache,log} \
  /usr/share/{doc,man,locale} \
  /var/cache/apt \
  /home/*/.cache \
  /root/.cache \
  /var/tmp/* \
  /tmp/* \
  "

Requirements:

  • Group related environment variables with descriptive comments
  • Use full command paths where applicable
  • Include all necessary flags for non-interactive operation
  • Use line continuation (\) for multi-line values
  • Define cleanup commands as reusable variables

4. Package Installation

4.1 Section Headers

Use clear section headers with 80+ # characters:

#################################################################################
# Base package and user configuration
#################################################################################

4.2 Package Lists

Define package lists using ARG for flexibility:

# Apt Packages
ARG APT_PKGS="\
  gh \
  git \
  tar \
  curl \
  tmux \
  "

4.3 Installation Pattern

RUN echo \
  && export TEST="[command] --version" \
  && ${apt_update} \
  && bash -c "${apt_install} --no-install-recommends -o Dpkg::Options::='--force-confold' ${APT_PKGS}" \
  && bash -c "${apt_clean}" \
  && ${dir_clean} \
  && ${TEST} \
  && echo

Requirements:

  • Start and end with echo for clean output
  • Define a TEST command for validation
  • Use environment variables for commands
  • Include cleanup steps
  • Execute the test command
  • One package per line in ARG lists with trailing backslash

5. Tool Installation Pattern

For external tools, use this standardized pattern:

RUN echo \
  && export NAME=toolname \
  && export TEST="${NAME} --version" \
  && export REPOSITORY="owner/repo" \
  && export VERSION="$(${curl} https://api.github.com/repos/${REPOSITORY}/releases/latest | jq --raw-output .tag_name)" \
  && export ARCH=$(uname -m | awk '{ if ($1 == "x86_64") print "amd64"; else if ($1 == "aarch64" || $1 == "arm64") print "arm64"; else print "unknown" }') \
  && export PKG="${NAME}-linux-${ARCH}.tar.gz" \
  && export URL="https://github.com/${REPOSITORY}/releases/download/${VERSION}/${PKG}" \
  && echo "---------------------------------------------------------"\
  && echo "INFO[${NAME}] Installed:" \
  && echo "INFO[${NAME}]   Command:        ${NAME}" \
  && echo "INFO[${NAME}]   Package:        ${PKG}" \
  && echo "INFO[${NAME}]   Latest Release: ${VERSION}" \
  && echo "INFO[${NAME}]   Architecture:   ${ARCH}" \
  && echo "INFO[${NAME}]   Source:         ${URL}" \
  && echo "---------------------------------------------------------"\
  && ${curl} ${URL} --output /tmp/${NAME} \
  && sudo ${INSTALL} /tmp/${NAME} ${BIN}/${NAME} \
  && ${dir_clean} \
  && ${TEST} \
  && echo

Requirements:

  • Use consistent variable names (NAME, TEST, REPOSITORY, VERSION, ARCH, PKG, URL)
  • Include architecture detection
  • Display installation information with formatted output
  • Use 57 - characters for separators
  • Download to /tmp and install to ${BIN}
  • Clean up after installation
  • Test the installation

6. User and Permission Management

6.1 Sudoers Configuration

RUN echo \
  && mkdir -p /etc/sudoers.d || true \
  && groupadd --force --system sudo || true \
  && groupadd --force --gid 127 --system docker \
  && echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" | tee /etc/sudoers.d/sudo \
  && echo

6.2 User Creation Pattern

RUN echo \
  && export USER_ID="1000" \
  && export USER_NAME="ubuntu" \
  && export USER_SHELL="bash" \
  && export USER_GROUPS="${USER_NAME},sudo,docker,ubuntu" \
  && export USER_GROUP_NAME="${USER_NAME}" \
  && export USER_GROUP_ID="${USER_ID}" \
  && echo "INFO[${USER_NAME}]  User:" \
  && echo "INFO[${USER_NAME}]    User Name:   ${USER_NAME}" \
  && echo "INFO[${USER_NAME}]    User Group:  ${USER_GROUP_NAME}" \
  && echo "INFO[${USER_NAME}]    Aux Groups:  ${USER_GROUPS}" \
  && echo "INFO[${USER_NAME}]    Group ID:    ${USER_GROUP_ID}" \
  && echo "INFO[${USER_NAME}]    User ID:     ${USER_ID}" \
  && echo "INFO[${USER_NAME}]    SHELL:       $(which ${USER_SHELL})" \
  && sudo groupadd --force --gid ${USER_ID} ${USER_NAME} \
  && sudo useradd --create-home --uid ${USER_ID} --gid ${USER_GROUP_ID} --shell $(which ${USER_SHELL}) --groups ${USER_GROUPS} ${USER_NAME} || true \
  && echo

Requirements:

  • Always use || true for operations that might fail safely
  • Display user configuration details
  • Use consistent UID/GID (1000 for primary user, 1001+ for additional users)
  • Set proper shell with $(which ${USER_SHELL})

7. Directory Structure

7.1 ADD vs COPY

# For directory trees
ADD slim/rootfs /
ADD slim/rootfs/etc/skel/ /root/
ADD slim/rootfs/etc/skel/ /home/ubuntu/

# For individual files
COPY ./bin/entrypoint /bin/

Requirements:

  • Use ADD for directory structures
  • Use COPY for individual files
  • Maintain consistent source paths

8. Cleanup and Optimization

8.1 Cleanup Pattern

Always clean up after operations:

&& ${dir_clean} \

8.2 The dir_clean Variable

This should remove:

  • APT caches: /var/lib/{apt,cache,log}
  • Documentation: /usr/share/{doc,man,locale}
  • User caches: /home/*/.cache, /root/.cache
  • Temporary files: /var/tmp/*, /tmp/*

9. Multi-Stage Builds

9.1 Stage Naming

FROM docker.io/gitlab/glab:latest as glab
FROM ghcr.io/containercraft/devcontainer:dind

9.2 Copying from Stages

COPY --from=glab /usr/bin/glab /usr/local/bin/glab

Requirements:

  • Use lowercase names for stages
  • Copy specific files, not entire directories when possible

10. Formatting Standards

10.1 Line Continuation

RUN echo \
  && command1 \
  && command2 \
  && echo

10.2 Separator Lines

  • Header/footer: 79 # characters
  • Section headers: 80+ # characters
  • Info output: 57 - characters

10.3 Spacing

  • Single blank line between major sections
  • No blank lines within RUN commands
  • Align \ characters when possible

10.4 Comments

# Single line comment for simple explanations

##################################################################################
# Major Section Header
# - Additional context line 1
# - Additional context line 2

11. Testing and Validation

11.1 Test Command Pattern

Every installation should define and execute a test:

&& export TEST="${NAME} --version" \
... (installation steps) ...
&& ${TEST} \

11.2 Common Test Commands

  • --version (preferred)
  • version (alternative)
  • --help (fallback)
  • command -v [name] (for commands without version flags)

12. Image Metadata

12.1 Final Metadata Block

#################################################################################
# Image Metadata
LABEL name="ContainerCraft Konductor Devcontainer"
LABEL io.k8s.display-name="ContainerCraft Konductor Devcontainer"
LABEL org.opencontainers.image.authors="github.com/ContainerCraft"
LABEL org.opencontainers.image.source="https://github.com/containercraft/devcontainer"
LABEL org.opencontainers.image.licenses="APACHE-2.0"
LABEL distribution-scope="public"

12.2 Entrypoint and Healthcheck

ENTRYPOINT ["/usr/bin/bash", "-c", "entrypoint"]
HEALTHCHECK --interval=120s --timeout=30s --start-period=5s --retries=3 CMD [ "true" ]

Version History

v1.0.0 (2024-12-04)

  • Initial release
  • Extracted patterns from ContainerCraft devcontainer Dockerfiles
  • Established core formatting and structure requirements
  • Defined standard installation patterns
  • Created consistent variable naming conventions

Future Considerations

  • VSCode extension for linting/formatting
  • Automated validation tools
  • Additional patterns for specific use cases
  • Performance optimization guidelines
  • Security scanning integration patterns

ContainerCraft Dockerfile Quick Reference v1.0.0

Essential Patterns

File Header

###############################################################################
# Use:
# - docker build --progress plain --tag ghcr.io/[org]/[image]:[tag] -f docker/[variant]/Dockerfile ./docker
# - docker run --rm -d --name [container] --hostname [hostname] ghcr.io/[org]/[image]:[tag]
###############################################################################

Base Setup

FROM ghcr.io/containercraft/devcontainer:base
LABEL tag="variant-name"
ENV DEVCONTAINER="variant-name"

Tool Installation Template

RUN echo \
  && export NAME=toolname \
  && export TEST="${NAME} --version" \
  && export REPOSITORY="owner/repo" \
  && export VERSION="$(${curl} https://api.github.com/repos/${REPOSITORY}/releases/latest | jq --raw-output .tag_name)" \
  && export ARCH=$(uname -m | awk '{ if ($1 == "x86_64") print "amd64"; else if ($1 == "aarch64" || $1 == "arm64") print "arm64"; else print "unknown" }') \
  && export PKG="${NAME}-linux-${ARCH}.tar.gz" \
  && export URL="https://github.com/${REPOSITORY}/releases/download/${VERSION}/${PKG}" \
  && echo "---------------------------------------------------------"\
  && echo "INFO[${NAME}] Installed:" \
  && echo "INFO[${NAME}]   Command:        ${NAME}" \
  && echo "INFO[${NAME}]   Package:        ${PKG}" \
  && echo "INFO[${NAME}]   Latest Release: ${VERSION}" \
  && echo "INFO[${NAME}]   Architecture:   ${ARCH}" \
  && echo "INFO[${NAME}]   Source:         ${URL}" \
  && echo "---------------------------------------------------------"\
  && ${curl} ${URL} --output /tmp/${NAME} \
  && sudo ${INSTALL} /tmp/${NAME} ${BIN}/${NAME} \
  && ${dir_clean} \
  && ${TEST} \
  && echo

APT Package Installation

ARG APT_PKGS="\
  package1 \
  package2 \
  "
RUN echo \
  && export TEST="command --version" \
  && ${apt_update} \
  && bash -c "${apt_install} --no-install-recommends -o Dpkg::Options::='--force-confold' ${APT_PKGS}" \
  && bash -c "${apt_clean}" \
  && ${dir_clean} \
  && ${TEST} \
  && echo

Formatting Rules

Element Length Character Usage
Header/Footer 79 # File boundaries
Section Header 80+ # Major sections
Info Separator 57 - Tool info blocks

Environment Variables (Defined in Slim)

ENV apt_update="sudo apt-get update"
ENV apt_install="TERM=linux DEBIAN_FRONTEND=noninteractive sudo apt-get install -q --yes --purge --assume-yes --auto-remove --allow-downgrades -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
ENV apt_clean="sudo apt-get clean && sudo apt-get autoremove -y && sudo apt-get purge -y --auto-remove"
ENV curl="/usr/bin/curl --silent --show-error --tlsv1.2 --location"
ENV curl_github="/usr/bin/curl --silent --show-error --tlsv1.2 --request GET --url"
ENV BIN="/usr/local/bin"
ENV INSTALL="install -m 755 -o root -g root"
ENV dir_clean="sudo rm -rf /var/lib/{apt,cache,log} /usr/share/{doc,man,locale} /var/cache/apt /home/*/.cache /root/.cache /var/tmp/* /tmp/*"

Architecture Detection

# Standard mapping
$(uname -m | awk '{ if ($1 == "x86_64") print "amd64"; else if ($1 == "aarch64" || $1 == "arm64") print "arm64"; else print "unknown" }')

# Alternative for some tools
$(uname -m | awk '{ if ($1 == "x86_64") print "x64"; else if ($1 == "aarch64" || $1 == "arm64") print "arm64"; else print "unknown" }')

# Direct architecture (ttyd)
$(uname -m | awk '{ if ($1 == "x86_64") print "x86_64"; else if ($1 == "aarch64" || $1 == "arm64") print "aarch64"; else print "unknown" }')

User Creation

# Primary user (UID 1000)
export USER_ID="1000"
export USER_NAME="ubuntu"

# Secondary users (UID 1001+)
export USER_ID="1001"
export USER_NAME="runner"

Common Test Commands

  • ${NAME} --version (preferred)
  • ${NAME} version (alternative)
  • command -v ${NAME} (existence check)
  • fc-list --quiet ${NAME} (fonts)

Cleanup Requirements

Always include in RUN commands:

  1. ${dir_clean} - Remove caches and temp files
  2. ${apt_clean} - Clean APT after installations
  3. || true - For operations that might safely fail

Section Comment Examples

#################################################################################
# Base package and user configuration
#################################################################################

##################################################################################
# Install Programming Language Tooling
# - python
#################################################################################

##################################################################################
# Install Tool Name
# - https://tool.website
# - https://github.com/owner/repo

Final Image Structure

# Load startup artifacts (if needed)
COPY ./bin/entrypoint /bin/

# Entrypoint & default command
ENTRYPOINT ["/usr/bin/bash", "-c", "entrypoint"]
HEALTHCHECK --interval=120s --timeout=30s --start-period=5s --retries=3 CMD [ "true" ]

#################################################################################
# Image Metadata
LABEL name="ContainerCraft Konductor Devcontainer"
LABEL io.k8s.display-name="ContainerCraft Konductor Devcontainer"
LABEL org.opencontainers.image.authors="github.com/ContainerCraft"
LABEL org.opencontainers.image.source="https://github.com/containercraft/devcontainer"
LABEL org.opencontainers.image.licenses="APACHE-2.0"
LABEL distribution-scope="public"
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ContainerCraft Dockerfile Linter Configuration",
"version": "1.0.0",
"type": "object",
"properties": {
"rules": {
"type": "object",
"properties": {
"header": {
"type": "object",
"properties": {
"required": { "type": "boolean", "default": true },
"separatorLength": { "type": "integer", "default": 79 },
"separatorChar": { "type": "string", "default": "#" },
"mustIncludeBuildCommand": { "type": "boolean", "default": true },
"mustIncludeRunCommand": { "type": "boolean", "default": true }
}
},
"metadata": {
"type": "object",
"properties": {
"requireLabelTag": { "type": "boolean", "default": true },
"requireEnvDevcontainer": { "type": "boolean", "default": true },
"labelTagMustMatchEnvDevcontainer": { "type": "boolean", "default": true }
}
},
"formatting": {
"type": "object",
"properties": {
"sectionHeaderMinLength": { "type": "integer", "default": 80 },
"infoSeparatorLength": { "type": "integer", "default": 57 },
"infoSeparatorChar": { "type": "string", "default": "-" },
"runCommandStartEnd": { "type": "string", "enum": ["echo"], "default": "echo" },
"lineContinuationAlignment": { "type": "boolean", "default": true }
}
},
"installation": {
"type": "object",
"properties": {
"requireTestCommand": { "type": "boolean", "default": true },
"requireCleanup": { "type": "boolean", "default": true },
"requireInfoBlock": { "type": "boolean", "default": true },
"standardVariables": {
"type": "array",
"default": ["NAME", "TEST", "REPOSITORY", "VERSION", "ARCH", "PKG", "URL"],
"items": { "type": "string" }
}
}
},
"environment": {
"type": "object",
"properties": {
"requireComments": { "type": "boolean", "default": true },
"predefinedCommands": {
"type": "object",
"default": {
"apt_update": "sudo apt-get update",
"apt_install": "TERM=linux DEBIAN_FRONTEND=noninteractive sudo apt-get install -q --yes --purge --assume-yes --auto-remove --allow-downgrades -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'",
"apt_clean": "sudo apt-get clean && sudo apt-get autoremove -y && sudo apt-get purge -y --auto-remove",
"curl": "/usr/bin/curl --silent --show-error --tlsv1.2 --location",
"curl_github": "/usr/bin/curl --silent --show-error --tlsv1.2 --request GET --url",
"dir_clean": "sudo rm -rf /var/lib/{apt,cache,log} /usr/share/{doc,man,locale} /var/cache/apt /home/*/.cache /root/.cache /var/tmp/* /tmp/*"
}
}
}
},
"users": {
"type": "object",
"properties": {
"primaryUserUID": { "type": "integer", "default": 1000 },
"secondaryUserStartUID": { "type": "integer", "default": 1001 },
"requireUserInfoDisplay": { "type": "boolean", "default": true }
}
},
"finalStructure": {
"type": "object",
"properties": {
"requireEntrypoint": { "type": "boolean", "default": true },
"requireHealthcheck": { "type": "boolean", "default": true },
"requireMetadataLabels": {
"type": "array",
"default": ["name", "io.k8s.display-name", "org.opencontainers.image.authors", "org.opencontainers.image.source"],
"items": { "type": "string" }
}
}
}
}
},
"severity": {
"type": "object",
"properties": {
"missingHeader": { "type": "string", "enum": ["error", "warning", "info"], "default": "error" },
"incorrectSeparatorLength": { "type": "string", "enum": ["error", "warning", "info"], "default": "warning" },
"missingTestCommand": { "type": "string", "enum": ["error", "warning", "info"], "default": "error" },
"missingCleanup": { "type": "string", "enum": ["error", "warning", "info"], "default": "warning" },
"nonStandardVariableNames": { "type": "string", "enum": ["error", "warning", "info"], "default": "warning" },
"missingInfoBlock": { "type": "string", "enum": ["error", "warning", "info"], "default": "info" }
}
},
"autoFix": {
"type": "object",
"properties": {
"enabled": { "type": "boolean", "default": true },
"fixSeparatorLength": { "type": "boolean", "default": true },
"addMissingEcho": { "type": "boolean", "default": true },
"alignLineContinuation": { "type": "boolean", "default": true },
"standardizeVariableNames": { "type": "boolean", "default": false }
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment