Skip to content

CI/CD and IaC Integration Guide

This guide covers using the dotfiles system in automated environments: CI/CD pipelines, Infrastructure as Code (IaC), container images, and configuration management tools.

The --unattended flag enables fully automated, zero-prompt installation suitable for:

  • Infrastructure as Code: Terraform, CloudFormation, Pulumi
  • Container Images: Docker, Podman
  • Configuration Management: Ansible, Chef, Puppet
  • CI/CD Pipelines: GitHub Actions, GitLab CI, Jenkins
  • VM Provisioning: Packer, Vagrant

When --unattended is set:

  • ✅ All interactive prompts are skipped
  • ✅ Default values are used for all module configurations
  • ✅ Secrets authentication is automatically skipped
  • ✅ Confirmation prompts are bypassed
  • ✅ Auto-detection works for non-interactive environments

The system automatically enables unattended mode when stdin is not interactive (e.g., curl | bash).

Note on example profiles. The repo ships developer, minimal, and test. Profile names used throughout this guide for illustration — server, ci, prod, docker, no-secrets — are not built in; create your own (a file in profiles/, or a profile in your content overlay) or substitute a real one like minimal. --profile also accepts a path to a profile file.

Terminal window
# Using bootstrap script
curl -sfL https://raw.githubusercontent.com/garygentry/dotfiles/main/bootstrap.sh | bash -s -- --unattended
# Direct installation
git clone https://github.com/garygentry/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
go build -o bin/dotfiles .
./bin/dotfiles install --unattended
Terminal window
# Create config.yml before installation
cat > ~/.dotfiles/config.yml <<EOF
profile: minimal
secrets:
provider: noop
EOF
# Run installation
dotfiles install --unattended

EC2 User Data Script:

#!/bin/bash
set -euo pipefail
# Install dependencies
apt-get update
apt-get install -y git golang-go
# Clone and install dotfiles
export USER=ubuntu
export HOME=/home/ubuntu
cd $HOME
git clone https://github.com/garygentry/dotfiles.git .dotfiles
cd .dotfiles
# Build and run
go build -o bin/dotfiles .
./bin/dotfiles install --unattended --profile server
# Verify installation
./bin/dotfiles status

Terraform Example:

resource "aws_instance" "server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
user_data = <<-EOF
#!/bin/bash
curl -sfL https://raw.githubusercontent.com/garygentry/dotfiles/main/bootstrap.sh | \
sudo -u ubuntu bash -s -- --unattended --profile server
EOF
tags = {
Name = "dotfiles-provisioned-server"
}
}

Dockerfile Example:

FROM ubuntu:22.04
# Install dependencies
RUN apt-get update && apt-get install -y \
git \
golang-go \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create user
RUN useradd -m -s /bin/bash developer
USER developer
WORKDIR /home/developer
# Clone dotfiles
RUN git clone https://github.com/garygentry/dotfiles.git .dotfiles
WORKDIR /home/developer/.dotfiles
# Build CLI
RUN go build -o bin/dotfiles .
# Install dotfiles in unattended mode
RUN ./bin/dotfiles install --unattended --profile minimal --skip-failed
# Set PATH
ENV PATH="/home/developer/.dotfiles/bin:${PATH}"
CMD ["/bin/bash"]

Multi-stage Build (Optimized):

# Build stage
FROM golang:1.23-alpine AS builder
WORKDIR /build
COPY . .
RUN go build -o dotfiles .
# Runtime stage
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
RUN useradd -m -s /bin/bash developer
USER developer
WORKDIR /home/developer
COPY --from=builder /build /home/developer/.dotfiles
WORKDIR /home/developer/.dotfiles
RUN ./dotfiles install --unattended --profile docker --skip-failed
ENV PATH="/home/developer/.dotfiles/bin:${PATH}"
CMD ["/bin/bash"]

Basic Playbook:

---
- name: Install dotfiles
hosts: servers
become: yes
become_user: "{{ target_user }}"
tasks:
- name: Install dependencies
apt:
name:
- git
- golang-go
state: present
become_user: root
- name: Clone dotfiles repository
git:
repo: https://github.com/garygentry/dotfiles.git
dest: "~/.dotfiles"
version: main
- name: Build dotfiles CLI
command: go build -o bin/dotfiles .
args:
chdir: "~/.dotfiles"
creates: "~/.dotfiles/bin/dotfiles"
- name: Install dotfiles
command: ./bin/dotfiles install --unattended --profile {{ dotfiles_profile | default('default') }}
args:
chdir: "~/.dotfiles"
register: dotfiles_install
changed_when: "'succeeded' in dotfiles_install.stdout"
- name: Verify installation
command: ./bin/dotfiles status
args:
chdir: "~/.dotfiles"
changed_when: false

With Role Structure:

roles/dotfiles/tasks/main.yml
---
- name: Ensure dependencies
apt:
name: [git, golang-go]
state: present
become: yes
- name: Clone dotfiles
git:
repo: "{{ dotfiles_repo }}"
dest: "{{ ansible_env.HOME }}/.dotfiles"
version: "{{ dotfiles_version | default('main') }}"
- name: Build CLI
command: go build -o bin/dotfiles .
args:
chdir: "{{ ansible_env.HOME }}/.dotfiles"
creates: "{{ ansible_env.HOME }}/.dotfiles/bin/dotfiles"
- name: Create config.yml
template:
src: config.yml.j2
dest: "{{ ansible_env.HOME }}/.dotfiles/config.yml"
when: dotfiles_config is defined
- name: Install modules
command: >
./bin/dotfiles install --unattended
{{ '--profile ' + dotfiles_profile if dotfiles_profile is defined else '' }}
{{ '--skip-failed' if dotfiles_skip_failed | default(false) else '' }}
args:
chdir: "{{ ansible_env.HOME }}/.dotfiles"
name: Test Dotfiles Installation
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test-install:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Build dotfiles
run: go build -o bin/dotfiles .
- name: Test unattended installation
run: |
./bin/dotfiles install --unattended --dry-run
./bin/dotfiles install --unattended --profile minimal --skip-failed
- name: Verify installation
run: ./bin/dotfiles status
- name: Test uninstall
run: ./bin/dotfiles uninstall git --unattended --dry-run

HCL2 Format:

packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
}
}
source "amazon-ebs" "dotfiles" {
ami_name = "dotfiles-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
instance_type = "t3.micro"
region = "us-east-1"
source_ami_filter {
filters = {
name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
}
build {
sources = ["source.amazon-ebs.dotfiles"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y git golang-go",
"git clone https://github.com/garygentry/dotfiles.git ~/.dotfiles",
"cd ~/.dotfiles && go build -o bin/dotfiles .",
"./bin/dotfiles install --unattended --profile server",
"./bin/dotfiles status"
]
}
}

Create configuration before installation to control profile and settings:

Terminal window
# Create config file
cat > ~/.dotfiles/config.yml <<EOF
profile: minimal
secrets:
provider: noop # no secrets backend (this is the default)
EOF
# Run installation
dotfiles install --unattended

To materialize identity, secrets choice, and custom profiles/modules inside a container or CI runner without committing them to the engine repo, point bootstrap.sh at your content repo. The bootstrap clones it and exports DOTFILES_CONTENT_DIR, which the dotfiles binary reads to deep-merge your overlay over the repo:

# In a Dockerfile / CI step — public, secret-free overlay
RUN curl -sfL https://raw.githubusercontent.com/garygentry/dotfiles/main/bootstrap.sh | \
bash -s -- --unattended --content-repo https://github.com/you/my-dotfiles.git

For an already-checked-out overlay, set the directory directly before installing:

Terminal window
export DOTFILES_CONTENT_DIR="$PWD/my-dotfiles"
dotfiles install --unattended --profile mine

Keep CI overlays public and secret-free; for a private overlay use an SSH agent or --content-auth-cmd. See the Content Overlay guide.

Create custom profiles for different environments (each profile file has a modules: key):

profiles/ci.yml
modules:
- git
- zsh
# profiles/server.yml
modules:
- git
- tmux
- zsh

Use with --profile flag (these names are examples — create the profile first, or use a built-in like minimal):

Terminal window
dotfiles install --unattended --profile ci

The system uses environment variables for configuration:

Terminal window
# Set dotfiles directory (default: ~/.dotfiles)
export DOTFILES_DIR=/opt/dotfiles
# Run installation
dotfiles install --unattended

Continue installation even if some modules fail:

Terminal window
dotfiles install --unattended --skip-failed

This is essential for container images where some modules (e.g., GUI tools) may not be compatible.

Stop immediately on first failure (useful for testing):

Terminal window
dotfiles install --unattended --fail-fast

Preview what would happen without making changes:

Terminal window
dotfiles install --unattended --dry-run
#!/bin/bash
set -euo pipefail
# Function to handle errors
install_dotfiles() {
if ! dotfiles install --unattended --skip-failed; then
echo "ERROR: Dotfiles installation failed"
dotfiles status # Show what succeeded
exit 1
fi
}
# Run with error handling
install_dotfiles
# Verify critical modules
if ! dotfiles status | grep -q "git.*installed"; then
echo "ERROR: Critical module 'git' not installed"
exit 1
fi
echo "Dotfiles installed successfully"

In unattended mode, secrets authentication is automatically skipped:

Terminal window
# Secrets authentication is skipped automatically
dotfiles install --unattended

For environments where secrets are needed:

Terminal window
# Authenticate before running dotfiles
op account add --address my.1password.com --email user@example.com
op signin
# Run installation
dotfiles install --unattended

Create a profile that excludes secrets-dependent modules:

profiles/no-secrets.yml
- git
- zsh
- tmux
# Note: 'ssh' module is excluded (requires 1password)

The dotfiles CLI uses standard exit codes:

  • 0: Success
  • 1: Failure
#!/bin/bash
if dotfiles install --unattended; then
echo "Installation successful"
else
echo "Installation failed with exit code $?"
exit 1
fi

Verify installation state:

Terminal window
# Check overall status
dotfiles status
# Check specific module
dotfiles status | grep git
# Programmatic check
if dotfiles status | grep -q "git.*installed"; then
echo "Git module is installed"
fi

Enable verbose logging for debugging:

Terminal window
# Verbose output
dotfiles install --unattended --verbose
# JSON logging (for log aggregation)
dotfiles install --unattended --log-json

Create environment-specific profiles:

Terminal window
# Development
profiles/dev.yml
# Production servers
profiles/prod.yml
# CI/CD
profiles/ci.yml
# Docker containers
profiles/docker.yml

Always test in dry-run mode first:

Terminal window
# Test installation
dotfiles install --unattended --profile prod --dry-run
# Review plan, then run
dotfiles install --unattended --profile prod

Container environments may not support all modules:

Terminal window
# Dockerfile
RUN ./bin/dotfiles install --unattended --skip-failed

Use specific Git tags or commits:

Terminal window
# Terraform user_data
git clone https://github.com/garygentry/dotfiles.git
git checkout v1.0.0

Save installation logs for debugging:

Terminal window
# Save verbose logs
dotfiles install --unattended --verbose > /var/log/dotfiles-install.log 2>&1
# JSON logs for aggregation
dotfiles install --unattended --log-json > /var/log/dotfiles.json

Always check status after installation:

#!/bin/bash
set -euo pipefail
dotfiles install --unattended --skip-failed
dotfiles status
# Verify critical modules
for module in git zsh; do
if ! dotfiles status | grep -q "${module}.*installed"; then
echo "ERROR: ${module} not installed"
exit 1
fi
done

Problem: Installation blocks waiting for input

Solution:

Terminal window
# Ensure --unattended is set
dotfiles install --unattended
# Check if stdin is being piped
echo "Installing..." | dotfiles install --unattended

Problem: Some modules require interactive terminal or system features

Solution:

Terminal window
# Use --skip-failed to continue
dotfiles install --unattended --skip-failed
# Or create a docker-specific profile
dotfiles install --unattended --profile docker

Problem: 1Password prompts block installation

Solution:

Terminal window
# Unattended mode auto-skips secrets
dotfiles install --unattended
# Or disable secrets in config
cat > config.yml <<EOF
secrets:
provider: noop
EOF

Problem: Go binary not in PATH in user data scripts

Solution:

Terminal window
# Add Go to PATH
export PATH="/usr/local/go/bin:$PATH"
go build -o bin/dotfiles .

Problem: Running as wrong user

Solution:

# Terraform - run as target user
user_data = <<-EOF
#!/bin/bash
sudo -u ubuntu bash -c '
cd ~
git clone https://github.com/garygentry/dotfiles.git .dotfiles
cd .dotfiles
go build -o bin/dotfiles .
./bin/dotfiles install --unattended
'
EOF
resource "aws_launch_template" "dotfiles" {
name_prefix = "dotfiles-"
image_id = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
user_data = base64encode(templatefile("${path.module}/user-data.sh", {
dotfiles_repo = "https://github.com/garygentry/dotfiles.git"
dotfiles_profile = "server"
dotfiles_version = "main"
}))
tag_specifications {
resource_type = "instance"
tags = {
Name = "dotfiles-server"
}
}
}
resource "aws_autoscaling_group" "dotfiles" {
desired_capacity = 2
max_size = 4
min_size = 1
launch_template {
id = aws_launch_template.dotfiles.id
version = "$Latest"
}
vpc_zone_identifier = var.subnet_ids
}

user-data.sh:

#!/bin/bash
set -euo pipefail
# Install dependencies
apt-get update
apt-get install -y git golang-go curl
# Clone and install as ubuntu user
sudo -u ubuntu bash <<'SCRIPT'
cd ~
git clone ${dotfiles_repo} .dotfiles
cd .dotfiles
git checkout ${dotfiles_version}
go build -o bin/dotfiles .
./bin/dotfiles install --unattended --profile ${dotfiles_profile} --skip-failed
# Verify
./bin/dotfiles status
SCRIPT
echo "Dotfiles installation complete"
.gitlab-ci.yml
stages:
- test
- build
test-dotfiles:
stage: test
image: ubuntu:22.04
before_script:
- apt-get update && apt-get install -y git golang-go
script:
- go build -o bin/dotfiles .
- ./bin/dotfiles install --unattended --dry-run
- ./bin/dotfiles install --unattended --profile ci --skip-failed
- ./bin/dotfiles status
artifacts:
when: on_failure
paths:
- .state/
expire_in: 1 week
build-image:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t myapp/dotfiles:latest .
- docker run myapp/dotfiles:latest dotfiles status

The --unattended flag makes the dotfiles system fully compatible with automated workflows:

  • ✅ Zero interactive prompts
  • ✅ Automatic fallback to defaults
  • ✅ Works in CI/CD, IaC, and containers
  • ✅ Compatible with all major cloud providers
  • ✅ Comprehensive error handling
  • ✅ Flexible profile system

For questions or issues, see the main README or Troubleshooting Guide.