Difficulty: Advanced | Time: 45 minutes | Focus: IncrementalSpec, custom configurations, tool integration
Objectives
By the end of this lab, you will understand the cleanimg-customize tool and IncrementalSpec format, write a YAML specification for image customization, generate a Dockerfile from the specification, build a customized CleanStart image, verify custom packages and configurations, and apply realistic customization patterns.
Prerequisites
The following are required: Docker 20.10 or newer, cleanimg-customize 0.3.0 or newer, a YAML editor or text editor, and curl for testing.
To get started, you'll need to follow the installation steps below. Download the executable from releases (example for Linux) by running the curl command:
# Download from releases (example for Linux)curl -L https://github.com/cleanstart/cleanimg-customize/releases/download/v0.3.0/cleanimg-customize-linux-amd64 \ -o /tmp/cleanimg-customizechmod +x /tmp/cleanimg-customizesudo mv /tmp/cleanimg-customize /usr/local/bin/cleanimg-customize # Verify installationcleanimg-customize --versionExpected output:
cleanimg-customize 0.3.0Background: IncrementalSpec
IncrementalSpec is a declarative YAML format that defines how to extend a CleanStart base image:
apiVersion: cleanimg/v1kind: IncrementalSpecmetadata: name: my-custom-imagespec: base_image: registry.cleanstart.com/cleanstart/python:3.12 arch: amd64 variant: prod packages: - name: curl - name: jq # ... more configuration ...This generates a multi-stage Dockerfile automatically, making image customization reproducible and version-controlled.
Step 1: Create Working Directory
mkdir -p ~/labs/lab-07-customize-imagecd ~/labs/lab-07-customize-imageStep 2: Create a Basic IncrementalSpec
Create spec.yaml:
cat > spec.yaml << 'EOF'apiVersion: cleanimg/v1kind: IncrementalSpecmetadata: name: lab-07-custom-python description: "Customized Python image with additional tools" version: "1.0.0" spec: base_image: registry.cleanstart.com/cleanstart/python:3.12 arch: amd64 variant: prod # Additional packages to install packages: - name: curl - name: ca-certificates - name: libffi-dev # Environment variables env_vars: APP_ENV: production PYTHONUNBUFFERED: "1" LOG_LEVEL: INFO # User configuration (as string UID:GID, not object) user: "65532:65532" # Labels for image metadata (as map, not list) labels: org.opencontainers.image.title: Lab-07-Custom-Python org.opencontainers.image.version: "1.0.0" org.opencontainers.image.source: "https://github.com/cleanstart/labs" EOFStep 3: Verify Installation and Validate the Specification
Verify that The Declarative Image Builder tool, cleanimg-customize, is installed:
cleanimg-customize --versionExpected output:
cleanimg-customize v0.3.0Now validate the YAML syntax and configuration:
cleanimg-customize validate --spec spec.yamlExpected output:
✅ Specification is valid - API version: cleanimg/v1 - Kind: IncrementalSpec - Base image: registry.cleanstart.com/cleanstart/python:3.12 - Packages to add: 3 - Environment variables: 3 - User configuration: present - Labels: 3If validation fails, check YAML syntax:
# Check YAML syntax (using any YAML validator)cat spec.yaml | head -20Step 4: Convert Spec to Dockerfile
Convert the spec to a Dockerfile to preview what will be generated:
cleanimg-customize to-dockerfile --spec spec.yaml --output Dockerfile.generatedExpected output:
✅ Dockerfile generated: Dockerfile.generatedView the generated Dockerfile:
cat Dockerfile.generatedExpected output (abbreviated):
# Generated by cleanimg-customize v0.3.0# Source: spec.yaml FROM registry.cleanstart.com/cleanstart/python:3.12 AS builder # Install packagesRUN apk add --no-cache \ curl \ ca-certificates \ libffi-dev # Stage 2: Final runtime imageFROM registry.cleanstart.com/cleanstart/python:3.12 COPY --from=builder /lib /libCOPY --from=builder /usr/lib /usr/libCOPY --from=builder /usr/bin /usr/bin # Set environment variablesENV APP_ENV=productionENV PYTHONUNBUFFERED=1ENV LOG_LEVEL=INFO # Create userRUN adduser -u 1000 -s /sbin/nologin -h /home/appuser appuser # LabelsLABEL org.opencontainers.image.title="Lab-07-Custom-Python"LABEL org.opencontainers.image.version="1.0.0"LABEL org.opencontainers.image.source="https://github.com/cleanstart/labs" USER appuserThe generated Dockerfile features a multi-stage build that is automatically created, with packages installed in the builder stage and libraries copied to the final stage for a lean image. Environment variables are set at build time, users are created with the specified UID, and labels are added for complete metadata.
Step 5: Build the Customized Image
Build the image directly from the spec:
cleanimg-customize build --spec spec.yaml --tag lab-07-custom:latestExpected output:
[+] Building 15.3s (10/10) FINISHED...=> => naming to docker.io/library/lab-07-custom:latestVerify the image was created:
docker images | grep lab-07-customExpected output:
lab-07-custom latest abcd1234efgh 20 seconds ago 250MBStep 6: Test the Customized Image
Run a test container to verify customizations:
docker run --rm lab-07-custom:latest python -c "import osprint('=== Lab 07: Customized Image Verification ===')print(f'APP_ENV: {os.getenv(\"APP_ENV\")}')print(f'PYTHONUNBUFFERED: {os.getenv(\"PYTHONUNBUFFERED\")}')print(f'LOG_LEVEL: {os.getenv(\"LOG_LEVEL\")}')print(f'UID: {os.getuid()}')print(f'Username: {os.getenv(\"USER\", \"unknown\")}')"Expected output:
=== Lab 07: Customized Image Verification ===APP_ENV: productionPYTHONUNBUFFERED: 1LOG_LEVEL: INFOUID: 1000Username: appuserVerify curl is available:
docker run --rm lab-07-custom:latest curl --versionExpected output:
curl 7.88.1 (x86_64-pc-linux-musl) ...Step 7: Create a More Complex Spec with Artifacts
Create an advanced specification with artifact injection. First, create the directory structure:
mkdir -p certs # Create a dummy CA certificate for demonstrationcat > certs/custom-ca.pem << 'EOF'-----BEGIN CERTIFICATE-----MIIDXTCCAkWgAwIBAgIJAJC1/iNAZwqDMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjQwMzIyMTUzMDAwWhcNMjUwMzIyMTUzMDAwWjBFMQswCQYDVQQGEwJVUzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Z3VS5JJcds3xfn5K2X5ZqZ5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z=-----END CERTIFICATE-----EOFCreate an advanced spec (spec-advanced.yaml):
cat > spec-advanced.yaml << 'EOF'apiVersion: cleanimg/v1kind: IncrementalSpecmetadata: name: lab-07-advanced-custom description: "Advanced customization with artifacts and certificates" version: "2.0.0" spec: base_image: registry.cleanstart.com/cleanstart/python:3.12 arch: amd64 variant: prod # Install additional packages packages: - name: curl - name: jq - name: ca-certificates - name: openssl - name: git - name: build-base # Copy local files into the image copy_files: - source: certs/custom-ca.pem destination: /etc/ssl/certs/custom-ca.pem permissions: "0644" # Environment variables for custom configuration env_vars: APP_ENV: production PYTHONUNBUFFERED: "1" SSL_CERT_FILE: /etc/ssl/certs/custom-ca.pem REQUESTS_CA_BUNDLE: /etc/ssl/certs/custom-ca.pem # User and permissions (as UID:GID string) user: "65532:65532" # Labels for image metadata (as map) labels: org.opencontainers.image.title: Lab-07-Advanced-Custom org.opencontainers.image.version: "2.0.0" org.opencontainers.image.description: "Custom Python image with certificates and tools" maintainer: "lab-07@cleanstart.dev" # Entry point for the container entrypoint: ["python"] cmd: ["-u"]EOFValidate the advanced spec:
cleanimg-customize validate --spec spec-advanced.yamlGenerate the advanced Dockerfile:
cleanimg-customize to-dockerfile --spec spec-advanced.yaml --output Dockerfile.advancedStep 8: Build and Test Advanced Image
Build the advanced customized image from the spec:
cleanimg-customize build --spec spec-advanced.yaml --tag lab-07-advanced:latestExpected output:
✅ Image built successfully: lab-07-advanced:latestVerify the certificate was injected:
docker run --rm lab-07-advanced:latest ls -la /etc/ssl/certs/custom-ca.pemExpected output:
-rw-r--r-- 1 root root 832 Mar 22 15:40 /etc/ssl/certs/custom-ca.pemVerify environment variables:
docker run --rm lab-07-advanced:latest python -c "import osprint('SSL_CERT_FILE:', os.getenv('SSL_CERT_FILE'))print('REQUESTS_CA_BUNDLE:', os.getenv('REQUESTS_CA_BUNDLE'))"Expected output:
SSL_CERT_FILE: /etc/ssl/certs/custom-ca.pemREQUESTS_CA_BUNDLE: /etc/ssl/certs/custom-ca.pemVerify additional tools are available:
docker run --rm lab-07-advanced:latest which curl jq gitExpected output:
/usr/bin/curl/usr/bin/jq/usr/bin/gitStep 9: Inspect Generated Images
Compare image sizes:
docker images | grep "lab-07"Expected output:
lab-07-advanced latest xyz123abcd 2 minutes ago 350MBlab-07-custom latest abcd1234efgh 5 minutes ago 250MBInspect image configuration:
docker inspect lab-07-custom:latest | grep -A 20 '"Env"'Expected output:
"Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "APP_ENV=production", "PYTHONUNBUFFERED=1", "LOG_LEVEL=INFO"],Check image labels:
docker inspect lab-07-custom:latest | grep -A 10 '"Labels"'Expected output:
"Labels": { "org.opencontainers.image.source": "https://github.com/cleanstart/labs", "org.opencontainers.image.title": "Lab-07-Custom-Python", "org.opencontainers.image.version": "1.0.0"}Step 10: Create a Build Script
Create a reusable build script:
cat > build-custom.sh << 'EOF'#!/bin/bash SPEC_FILE=${1:-spec.yaml}IMAGE_NAME=${2:-lab-07-custom}IMAGE_TAG=${3:-latest} if [ ! -f "$SPEC_FILE" ]; then echo "❌ Specification file not found: $SPEC_FILE" exit 1fi echo "Building customized image from $SPEC_FILE..." # Validateecho "1. Validating specification..."cleanimg-customize validate --spec "$SPEC_FILE" || exit 1 # Buildecho "2. Building image from spec..."cleanimg-customize build --spec "$SPEC_FILE" --tag "$IMAGE_NAME:$IMAGE_TAG" || exit 1 # Verifyecho "3. Verifying image..."docker images | grep "$IMAGE_NAME" || exit 1 echo "✅ Image built successfully: $IMAGE_NAME:$IMAGE_TAG"EOF chmod +x build-custom.shTest the build script:
./build-custom.sh spec.yaml lab-07-custom v1.0.0Expected output:
Building customized image from spec.yaml...1. Validating specification...✅ Specification is valid2. Generating Dockerfile...✅ Dockerfile generated: Dockerfile.generated3. Building Docker image...[+] Building 2.3s (6/6) FINISHED4. Verifying image...lab-07-custom v1.0.0 xyz123abcd 5 seconds ago 250MB✅ Image built successfully: lab-07-custom:v1.0.0Step 11: Create Documentation
Create a customization guide:
cat > CUSTOMIZATION_GUIDE.md << 'EOF'# Image Customization Guide ## IncrementalSpec Format ### Minimal Example```yamlapiVersion: cleanimg/v1kind: IncrementalSpecmetadata: name: my-imagespec: base_image: registry.cleanstart.com/cleanstart/python:3.12 arch: amd64 variant: prodCommon Customizations
Add Packages
packages: add:curl, jq, and git.Set Environment Variables
environment:name: APP_ENV and name: DEBUG. value: "false"Create User
user: name: appuser uid: 1000 gid: 1000Inject Artifacts
artifacts:source: ./config/app.conf destination: /etc/app/app.conf permissions: "0644"Add Labels
labels:name: org.opencontainers.image.version value: "1.0.0"Workflow
- Create spec.yaml — Define customizations
- Validate —
cleanimg-customize validate --spec spec.yaml - Preview —
cleanimg-customize to-dockerfile --spec spec.yaml --output Dockerfile - Build —
cleanimg-customize build --spec spec.yaml --tag image:tag - Test —
docker run --rm image:tag <test-commands>
Best Practices
- Version your specifications
- Validate before building
- Test customized images thoroughly
- Document all custom configurations
- Use meaningful labels
- Keep artifacts minimal
- Minimize added packages
- Run as non-root user
Example Commands
# Validatecleanimg-customize validate --spec spec.yaml # Preview Dockerfilecleanimg-customize to-dockerfile --spec spec.yaml --output Dockerfile # Build directly from speccleanimg-customize build --spec spec.yaml --tag my-image:latest # Testdocker run --rm my-image:latest python -c "import sys; print(sys.version)"EOF
cat CUSTOMIZATION_GUIDE.md
--- ## Verification Checklist Confirm that the directory `~/labs/lab-07-customize-image` has been created, cleanimg-customize 0.3.0 or later is installed, `spec.yaml` is created with basic customization, `cleanimg-customize validate` passes without errors, `cleanimg-customize to-dockerfile` creates a Dockerfile successfully, `Dockerfile.generated` includes all customizations, the Docker image builds successfully, the container displays correct environment variables, the container shows the correct UID of 65532, curl and other packages are available in the container, `spec-advanced.yaml` is created with artifacts, the advanced image includes a custom certificate, the `build-custom.sh` script is created and functions correctly, and `CUSTOMIZATION_GUIDE.md` is created with complete documentation. If all items are confirmed, you have successfully completed Lab 07. --- ## What You Learned Through this lab, you have learned the IncrementalSpec format, which provides a declarative YAML approach to image customization, and the cleanimg-customize tool for automated Dockerfile generation. You understand multi-stage builds as automatically generated for efficiency, package management for adding APK packages to images, and environment variables for setting configuration. You have learned user management for creating non-root users with specific UIDs, artifact injection for copying files into images, image labels for OCI-compliant metadata, and the value of version-controlled image definitions for reproducibility. Finally, you have learned build automation techniques for scripting the full customization workflow end-to-end. --- ## Cleanup Remove built images: ```bashdocker rmi lab-07-custom:latest lab-07-advanced:latestRemove lab directory (optional):
rm -rf ~/labs/lab-07-customize-imageNext Lab
Proceed to Lab 08: Compliance Audit to perform security audits on running containers and verify compliance with security standards.
Real-World Applications
CleanStart image customization enables the creation of standardized base images across the organization with required tools baked in. You can add compliance hardening through security tools and certificates. Multi-language support is simplified by customizing language base images for specific needs. You can generate different images for development versus production environments from a single specification. Language-specific build tools can be included for polyglot development teams. Configuration files can be injected at build time to handle environment-specific runtime requirements.
Estimated Time: 45 minutes | Hands-on: ~35 minutes | Reading: ~10 minutes
