Last active
July 14, 2025 16:45
-
-
Save andr-ec/26b167d5f8422b5cf78a98bb4ac8b7e6 to your computer and use it in GitHub Desktop.
A shell script that generates a dockerfile from a devbox.json and devbox.lock using nixery.dev
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # generate-dockerfile.sh | |
| # Creates a base Dockerfile from devbox.json and devbox.lock files | |
| # Usage: ./generate-dockerfile.sh [devbox.json] [devbox.lock] [output-dockerfile] | |
| set -euo pipefail | |
| # Help function | |
| show_help() { | |
| cat << EOF | |
| generate-dockerfile.sh - Generate Docker images from devbox configurations | |
| USAGE: | |
| ./generate-dockerfile.sh [devbox.json] [devbox.lock] [output-dockerfile] | |
| ARGUMENTS: | |
| devbox.json Path to devbox.json file (default: devbox.json) | |
| devbox.lock Path to devbox.lock file (default: devbox.lock) | |
| output-dockerfile Output Dockerfile path (default: Dockerfile.generated) | |
| EXAMPLES: | |
| # Generate Dockerfile with default files | |
| ./generate-dockerfile.sh | |
| # Specify custom files | |
| ./generate-dockerfile.sh my-devbox.json my-devbox.lock MyDockerfile | |
| # Generate with different output name | |
| ./generate-dockerfile.sh devbox.json devbox.lock Dockerfile.custom | |
| REQUIREMENTS: | |
| - jq (for JSON parsing) | |
| - Valid devbox.json and devbox.lock files | |
| NOTES: | |
| - Generated Dockerfile uses nixery.dev with specific commit hashes | |
| - Review and customize TODO sections in generated Dockerfile | |
| - All packages from devbox.json will be included | |
| EOF | |
| } | |
| # Check for help flag | |
| if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then | |
| show_help | |
| exit 0 | |
| fi | |
| # Default file paths | |
| DEVBOX_JSON="${1:-devbox.json}" | |
| DEVBOX_LOCK="${2:-devbox.lock}" | |
| OUTPUT_DOCKERFILE="${3:-Dockerfile.generated}" | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| NC='\033[0m' # No Color | |
| log_info() { | |
| echo -e "${GREEN}[INFO]${NC} $1" | |
| } | |
| log_warn() { | |
| echo -e "${YELLOW}[WARN]${NC} $1" | |
| } | |
| log_error() { | |
| echo -e "${RED}[ERROR]${NC} $1" | |
| } | |
| # Check if required files exist | |
| if [[ ! -f "$DEVBOX_JSON" ]]; then | |
| log_error "devbox.json not found at: $DEVBOX_JSON" | |
| exit 1 | |
| fi | |
| if [[ ! -f "$DEVBOX_LOCK" ]]; then | |
| log_error "devbox.lock not found at: $DEVBOX_LOCK" | |
| exit 1 | |
| fi | |
| # Check if jq is available | |
| if ! command -v jq &> /dev/null; then | |
| log_error "jq is required but not installed. Please install jq first." | |
| exit 1 | |
| fi | |
| log_info "Reading packages from $DEVBOX_JSON..." | |
| # Extract packages from devbox.json | |
| PACKAGES=$(jq -r '.packages[]' "$DEVBOX_JSON" | sed 's/@latest$//') | |
| if [[ -z "$PACKAGES" ]]; then | |
| log_error "No packages found in $DEVBOX_JSON" | |
| exit 1 | |
| fi | |
| log_info "Found packages: $(echo $PACKAGES | tr '\n' ' ')" | |
| # Function to get commit hash for a package from devbox.lock | |
| get_commit_hash() { | |
| local package="$1" | |
| local package_key="${package}@latest" | |
| # Extract the commit hash from the resolved field | |
| local resolved=$(jq -r --arg pkg "$package_key" '.packages[$pkg].resolved // empty' "$DEVBOX_LOCK") | |
| if [[ -n "$resolved" && "$resolved" != "null" ]]; then | |
| # Extract commit hash from GitHub URL (format: github:NixOS/nixpkgs/COMMITHASH#package) | |
| echo "$resolved" | sed -n 's/.*github:NixOS\/nixpkgs\/\([^#]*\)#.*/\1/p' | |
| else | |
| log_warn "No commit found for package: $package" | |
| echo "" | |
| fi | |
| } | |
| log_info "Generating Dockerfile..." | |
| # Start generating the Dockerfile | |
| cat > "$OUTPUT_DOCKERFILE" << 'EOF' | |
| # Auto-generated Dockerfile from devbox.json and devbox.lock | |
| # Generated on: $(date) | |
| # | |
| # This is a base Dockerfile - modify as needed for your specific application | |
| EOF | |
| # Add package comments | |
| echo "# Packages and their specific nixpkgs commits:" >> "$OUTPUT_DOCKERFILE" | |
| stage_counter=1 | |
| declare -a stages=() | |
| declare -a stage_names=() | |
| for package in $PACKAGES; do | |
| commit=$(get_commit_hash "$package") | |
| if [[ -n "$commit" ]]; then | |
| echo "# $package: commit $commit" >> "$OUTPUT_DOCKERFILE" | |
| # Create stage name (sanitize package name for Docker) | |
| stage_name=$(echo "$package" | tr '-' '_' | tr '.' '_') | |
| stage_names+=("$stage_name") | |
| stages+=("$package:$commit") | |
| else | |
| log_warn "Skipping $package - no commit hash found" | |
| fi | |
| done | |
| echo "" >> "$OUTPUT_DOCKERFILE" | |
| # Group packages by commit hash | |
| declare -A commit_groups | |
| for i in "${!stages[@]}"; do | |
| package_info="${stages[$i]}" | |
| package=$(echo "$package_info" | cut -d':' -f1) | |
| commit=$(echo "$package_info" | cut -d':' -f2) | |
| if [[ -n "${commit_groups[$commit]+isset}" ]]; then | |
| commit_groups[$commit]="${commit_groups[$commit]}/$package" | |
| else | |
| commit_groups[$commit]="$package" | |
| fi | |
| done | |
| # Find the most common commit hash and packages that can be grouped | |
| max_count=0 | |
| main_commit="" | |
| for commit in "${!commit_groups[@]}"; do | |
| count=$(echo "${commit_groups[$commit]}" | tr '/' '\n' | wc -l) | |
| if [[ $count -gt $max_count ]]; then | |
| max_count=$count | |
| main_commit=$commit | |
| fi | |
| done | |
| # Generate main tools stage with grouped packages | |
| echo "# Stage 1: Main tools" >> "$OUTPUT_DOCKERFILE" | |
| if [[ -n "$main_commit" ]]; then | |
| main_packages="shell/${commit_groups[$main_commit]}" | |
| echo "FROM nixery.dev/$main_packages:$main_commit AS base" >> "$OUTPUT_DOCKERFILE" | |
| echo "# Grouped packages with commit $main_commit: ${commit_groups[$main_commit]}" >> "$OUTPUT_DOCKERFILE" | |
| else | |
| echo "FROM nixery.dev/shell AS base" >> "$OUTPUT_DOCKERFILE" | |
| fi | |
| echo "" >> "$OUTPUT_DOCKERFILE" | |
| # Generate individual package stages for packages not in main group | |
| stage_counter=2 | |
| for i in "${!stages[@]}"; do | |
| package_info="${stages[$i]}" | |
| package=$(echo "$package_info" | cut -d':' -f1) | |
| commit=$(echo "$package_info" | cut -d':' -f2) | |
| stage_name="${stage_names[$i]}" | |
| # Skip packages already included in main tools stage | |
| if [[ "$commit" == "$main_commit" ]]; then | |
| continue | |
| fi | |
| echo "# Stage $stage_counter: $package with specific commit" >> "$OUTPUT_DOCKERFILE" | |
| echo "FROM nixery.dev/$package:$commit AS ${stage_name}_layer" >> "$OUTPUT_DOCKERFILE" | |
| echo "" >> "$OUTPUT_DOCKERFILE" | |
| ((stage_counter++)) | |
| done | |
| # Generate final stage | |
| cat >> "$OUTPUT_DOCKERFILE" << 'EOF' | |
| # Final stage: Combine all packages | |
| FROM base | |
| EOF | |
| # Add COPY commands only for packages not in main group | |
| echo "# Copy nix store from each package stage" >> "$OUTPUT_DOCKERFILE" | |
| for i in "${!stages[@]}"; do | |
| package_info="${stages[$i]}" | |
| commit=$(echo "$package_info" | cut -d':' -f2) | |
| stage_name="${stage_names[$i]}" | |
| # Skip packages already included in main tools stage | |
| if [[ "$commit" == "$main_commit" ]]; then | |
| continue | |
| fi | |
| echo "COPY --from=${stage_name}_layer /nix /nix" >> "$OUTPUT_DOCKERFILE" | |
| done | |
| echo "" >> "$OUTPUT_DOCKERFILE" | |
| # Add profile creation and environment setup | |
| cat >> "$OUTPUT_DOCKERFILE" << 'EOF' | |
| # Make binaries available in PATH using selective linking | |
| RUN mkdir -p /bin && \ | |
| for pattern in nodejs ffmpeg elixir erlang beam python ruby go rust; do \ | |
| ln -sf /nix/store/*$pattern*/bin/* /bin/ 2>/dev/null || true; \ | |
| done && \ | |
| # Also link any other binaries that might be present | |
| for store_path in /nix/store/*/bin; do \ | |
| if [ -d "$store_path" ]; then \ | |
| ln -sf "$store_path"/* /bin/ 2>/dev/null || true; \ | |
| fi; \ | |
| done | |
| # Set up environment | |
| ENV PATH=/bin:/usr/bin:$PATH | |
| ENV LANG=C.UTF-8 | |
| ENV LC_ALL=C.UTF-8 | |
| # Create working directory | |
| WORKDIR /app | |
| # TODO: Add your application-specific environment variables here | |
| # ENV MY_APP_ENV=production | |
| # ENV MY_APP_PORT=8080 | |
| # TODO: Copy your application files | |
| # COPY . . | |
| # TODO: Install dependencies or build your application | |
| # RUN npm install && npm run build | |
| # RUN pip install -r requirements.txt | |
| # RUN go mod download && go build | |
| # TODO: Set up any additional configuration | |
| # COPY config/ ./config/ | |
| # TODO: Expose ports as needed | |
| # EXPOSE 8080 | |
| # Default command (modify as needed) | |
| CMD ["/bin/bash"] | |
| EOF | |
| # Replace the date placeholder | |
| sed -i "s/\$(date)/$(date)/" "$OUTPUT_DOCKERFILE" | |
| log_info "Dockerfile generated successfully: $OUTPUT_DOCKERFILE" | |
| log_info "" | |
| log_info "Next steps:" | |
| log_info "1. Review the generated Dockerfile" | |
| log_info "2. Uncomment and modify the TODO sections for your application" | |
| log_info "3. Add your application-specific build steps" | |
| log_info "4. Test with: docker build -t my-app ." | |
| log_info "" | |
| log_warn "Remember to:" | |
| log_warn "- Add your application files (COPY commands)" | |
| log_warn "- Set proper environment variables" | |
| log_warn "- Install application dependencies (uncomment appropriate package manager)" | |
| log_warn "- Configure the correct CMD or ENTRYPOINT" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I generated this script with AI, double check it works before using it
Generate Dockerfile from Devbox
A shell script that automatically generates a base Dockerfile from
devbox.jsonanddevbox.lockfiles, using specific nixpkgs commits for reproducible builds.Requirements
jqfor JSON parsingdevbox.jsonanddevbox.lockfilesUsage
Example Output
The script generates a Dockerfile like this:
Customization
After generating the Dockerfile, customize the TODO sections:
Add your application files:
COPY . .Install dependencies (uncomment as needed):
Set environment variables:
Expose ports:
EXPOSE 8080Set the correct command:
How It Works
devbox.json(excluding PostgreSQL)devbox.lockfor each packageBuilding the Generated Dockerfile
Notes