Skip to content

Commit 4efb20c

Browse files
authored
feat: unified shell + sandbox setup with Copier, devcontainer CLI, and robust features (#43)
Signed-off-by: Jonatan Mata <jonmatum@gmail.com>
1 parent fd3fa90 commit 4efb20c

File tree

12 files changed

+469
-130
lines changed

12 files changed

+469
-130
lines changed

.copier-answers.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
features:
2+
- shell
3+
image: debian:latest

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ Thumbs.db
2727
*.swo
2828

2929
.sandbox/
30+
.venv-tools/

.template/copier.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
_subdirectory: devcontainer_template
2+
_questions:
3+
features:
4+
type: yaml
5+
help: "List of features to include"
6+
default: ["shell"]
7+
image:
8+
type: str
9+
help: "Base image to use"
10+
default: "ubuntu:latest"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "Feature Test Sandbox",
3+
"image": "{{ image }}",
4+
"features": {
5+
{% for feature in features %}
6+
"./features/{{ feature }}": {
7+
{% if feature == "shell" %}
8+
"timezone": "America/Costa_Rica",
9+
"opinionated": true
10+
{% endif %}
11+
}{% if not loop.last %},{% endif %}
12+
{% endfor %}
13+
}
14+
}

LICENSE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
MIT License
22

33
Copyright (c) 2022 Microsoft Corporation
4+
Copyright (c) 2025 Jonatan Mata
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy
67
of this software and associated documentation files (the "Software"), to deal

Makefile

Lines changed: 97 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,122 @@
1+
# Configuration
12
SANDBOX_DIR = .sandbox
3+
SANDBOX_ANSWERS_FILE = $(SANDBOX_DIR)/answers.yml
4+
COPIER_VENV = .venv-tools
5+
COPIER_BIN = $(COPIER_VENV)/bin/copier
26
FEATURES ?= shell
37
IMAGE ?= ubuntu:latest
48
IMAGES = ubuntu:latest debian:latest amazonlinux:2023 mcr.microsoft.com/devcontainers/base:ubuntu
9+
ANSWERS_FILE = .copier-answers.yml
510

6-
.PHONY: help sandbox clean build up rebuild exec stop logs interactive
11+
.PHONY: help sandbox clean build up rebuild exec stop logs interactive ensure-copier format validate
712

13+
# Help Documentation
814
help:
915
@echo ""
10-
@echo "\033[1;36mAvailable make commands:\033[0m"
11-
@echo "--------------------------------------"
12-
@echo "\033[1;36mSandbox Management:\033[0m"
13-
@printf " %-28s - %s\n" "make sandbox" "Create sandbox for features"
14-
@printf " %-28s - %s\n" "make clean" "Remove sandbox"
16+
@echo "Available make commands"
17+
@echo "------------------------"
18+
@echo "Sandbox Management:"
19+
@printf " %-28s %s\n" "make sandbox" "Create sandbox using Copier and answers file"
20+
@printf " %-28s %s\n" "make interactive" "Prompt user to create .copier-answers.yml"
21+
@printf " %-28s %s\n" "make clean" "Remove the generated sandbox directory"
1522
@echo ""
16-
@echo "\033[1;36mDevcontainer Lifecycle:\033[0m"
17-
@printf " %-28s - %s\n" "make devcontainer-up" "Build/start devcontainer"
18-
@printf " %-28s - %s\n" "make build" "Build the container only (no attach)"
19-
@printf " %-28s - %s\n" "make up" "Start/attach to running container"
20-
@printf " %-28s - %s\n" "make rebuild" "Clean + re-sandbox + up"
21-
@printf " %-28s - %s\n" "make stop" "Stop the devcontainer"
22-
@printf " %-28s - %s\n" "make logs" "Show container logs"
23+
@echo "Devcontainer Lifecycle:"
24+
@printf " %-28s %s\n" "make devcontainer-up" "Create and run devcontainer"
25+
@printf " %-28s %s\n" "make build" "Build devcontainer image only"
26+
@printf " %-28s %s\n" "make up" "Alias to 'devcontainer-up'"
27+
@printf " %-28s %s\n" "make exec CMD='zsh'" "Execute command in container"
2328
@echo ""
24-
@echo "\033[1;36mDevcontainer Utilities:\033[0m"
25-
@printf " %-28s - %s\n" "make exec CMD=\"zsh\"" "Execute a command in the devcontainer"
26-
@printf " %-28s - %s\n" "make interactive" "Interactive feature/image selection"
29+
@echo "Utility and Maintenance:"
30+
@printf " %-28s %s\n" "make format" "Format devcontainer.json using jq"
31+
@printf " %-28s %s\n" "make container-id" "Print running devcontainer ID"
2732
@echo ""
28-
@echo "\033[1;36mUsage Examples:\033[0m"
29-
@echo "--------------------------------------"
30-
@echo " make sandbox FEATURES=\"shell hello\" IMAGE=<image>"
31-
@echo " make devcontainer-up FEATURES=\"shell hello\" IMAGE=<image>"
33+
@echo "Reference:"
34+
@echo " - Features live under ./src/<feature>"
35+
@echo " - Base images: $(IMAGES)"
3236
@echo ""
37+
@echo "Examples:"
38+
@echo " make sandbox FEATURES=\"shell hello\" IMAGE=debian:latest"
39+
@echo " make devcontainer-up"
3340

41+
# List available feature folders
3442
list-features:
3543
@echo "Available features:"
3644
@cd src && find . -maxdepth 1 -mindepth 1 -type d | sed 's|^\./||' | sort | nl
3745

46+
# List supported base images
3847
list-images:
3948
@echo "Available base images:"
4049
@echo "$(IMAGES)" | tr ' ' '\n' | sort | nl
4150

42-
sandbox:
43-
@echo "Creating sandbox environment for features: $(FEATURES) with base image: $(IMAGE)"
51+
# Ensure Copier is installed in a local virtualenv
52+
ensure-copier:
53+
@echo "Checking for Copier..."
54+
@if [ ! -x "$(COPIER_BIN)" ]; then \
55+
echo "Copier not found. Creating virtualenv..."; \
56+
if ! command -v python3 >/dev/null 2>&1; then \
57+
echo "python3 is required but not found. Please install it."; \
58+
exit 1; \
59+
fi; \
60+
python3 -m venv $(COPIER_VENV); \
61+
. $(COPIER_VENV)/bin/activate && pip install --upgrade pip copier; \
62+
echo "Copier installed in $(COPIER_VENV)"; \
63+
else \
64+
echo "Copier already available."; \
65+
fi
66+
67+
# Generate the sandbox with Copier
68+
sandbox: ensure-copier
69+
@if [ ! -f $(ANSWERS_FILE) ]; then \
70+
echo "No $(ANSWERS_FILE) found. Running interactive setup..."; \
71+
$(MAKE) interactive; \
72+
else \
73+
read -p "Use existing $(ANSWERS_FILE)? [Y/n]: " CONFIRM; \
74+
if [ "$$CONFIRM" = "n" ] || [ "$$CONFIRM" = "N" ]; then \
75+
$(MAKE) interactive; \
76+
fi; \
77+
fi
78+
@echo "Using $(ANSWERS_FILE) to generate sandbox..."
4479
rm -rf $(SANDBOX_DIR)
80+
mkdir -p $(SANDBOX_DIR)
81+
@echo "Copying feature definitions into sandbox..."
4582
mkdir -p $(SANDBOX_DIR)/.devcontainer/features
46-
@for feature in $(FEATURES); do \
47-
cp -r src/$$feature $(SANDBOX_DIR)/.devcontainer/features/$$feature; \
48-
done
49-
@echo '{' > $(SANDBOX_DIR)/.devcontainer/devcontainer.json
50-
@echo ' "name": "Feature Test Sandbox",' >> $(SANDBOX_DIR)/.devcontainer/devcontainer.json
51-
@echo ' "image": "$(IMAGE)",' >> $(SANDBOX_DIR)/.devcontainer/devcontainer.json
52-
@echo ' "features": {' >> $(SANDBOX_DIR)/.devcontainer/devcontainer.json
53-
@features_array=$(FEATURES); \
54-
for feature in $$features_array; do \
55-
echo " \"./features/$$feature\": {}," >> $(SANDBOX_DIR)/.devcontainer/devcontainer.json; \
56-
done
57-
sed -i '' '$$s/,$$//' $(SANDBOX_DIR)/.devcontainer/devcontainer.json
58-
@echo ' }' >> $(SANDBOX_DIR)/.devcontainer/devcontainer.json
59-
@echo '}' >> $(SANDBOX_DIR)/.devcontainer/devcontainer.json
60-
@echo "Sandbox created at $(SANDBOX_DIR)/"
83+
cp -R src/* $(SANDBOX_DIR)/.devcontainer/features/
84+
cp $(ANSWERS_FILE) $(SANDBOX_ANSWERS_FILE)
85+
$(COPIER_BIN) copy -a $(notdir $(SANDBOX_ANSWERS_FILE)) .template $(SANDBOX_DIR)
86+
@$(MAKE) format
87+
@jq . $(SANDBOX_DIR)/.devcontainer/devcontainer.json
88+
89+
90+
# Prompt user to select features and base image, then generate answers file
91+
interactive: ensure-copier
92+
@$(MAKE) list-features
93+
@read -p "Select feature numbers (space-separated): " NUMS; \
94+
FEATURES_SELECTED=""; \
95+
for n in $$NUMS; do \
96+
F=$$(cd src && find . -maxdepth 1 -mindepth 1 -type d | sed 's|^\./||' | sort | sed -n "$${n}p"); \
97+
FEATURES_SELECTED="$$FEATURES_SELECTED $$F"; \
98+
done; \
99+
echo ""; \
100+
$(MAKE) list-images; \
101+
read -p "Select image number: " IMG_NUM; \
102+
IMAGE_SELECTED=$$(echo "$(IMAGES)" | tr ' ' '\n' | sort | sed -n "$${IMG_NUM}p"); \
103+
echo ""; \
104+
echo "You selected: $$FEATURES_SELECTED for image $$IMAGE_SELECTED"; \
105+
echo "features:" > $(ANSWERS_FILE); \
106+
for f in $$FEATURES_SELECTED; do echo " - $$f"; done >> $(ANSWERS_FILE); \
107+
echo "image: $$IMAGE_SELECTED" >> $(ANSWERS_FILE); \
108+
echo "$(ANSWERS_FILE) generated."
61109

110+
# Clean the sandbox directory
62111
clean:
63112
@echo "Cleaning sandbox..."
64113
rm -rf $(SANDBOX_DIR)
65114
@echo "Sandbox cleaned."
66115

116+
# Devcontainer lifecycle commands
117+
67118
devcontainer-up: sandbox
68-
@echo "Launching devcontainer for features $(FEATURES) using image $(IMAGE)..."
119+
@echo "Launching devcontainer..."
69120
cd $(SANDBOX_DIR) && devcontainer up --workspace-folder .
70121

71122
build:
@@ -74,30 +125,15 @@ build:
74125
up:
75126
cd $(SANDBOX_DIR) && devcontainer up --workspace-folder .
76127

77-
rebuild: clean devcontainer-up
78-
79128
exec:
80129
cd $(SANDBOX_DIR) && devcontainer exec --workspace-folder . -- $(CMD)
81130

82-
stop:
83-
cd $(SANDBOX_DIR) && devcontainer stop --workspace-folder .
84-
85-
logs:
86-
cd $(SANDBOX_DIR) && devcontainer logs --workspace-folder .
131+
# Format devcontainer.json using jq
132+
format:
133+
@echo "Formatting devcontainer.json..."
134+
@jq . $(SANDBOX_DIR)/.devcontainer/devcontainer.json > $(SANDBOX_DIR)/.devcontainer/devcontainer.json.tmp && \
135+
mv $(SANDBOX_DIR)/.devcontainer/devcontainer.json.tmp $(SANDBOX_DIR)/.devcontainer/devcontainer.json && \
136+
echo "Formatted successfully."
87137

88-
interactive:
89-
@$(MAKE) list-features
90-
@read -p "Select feature numbers (space-separated): " FEATURES_NUMBERS; \
91-
FEATURES_SELECTED=""; \
92-
for num in $$FEATURES_NUMBERS; do \
93-
FEATURE=$$(cd src && find . -maxdepth 1 -mindepth 1 -type d | sed 's|^\./||' | sort | sed -n "$${num}p"); \
94-
FEATURES_SELECTED="$$FEATURES_SELECTED $$FEATURE"; \
95-
done; \
96-
echo ""; \
97-
$(MAKE) list-images; \
98-
read -p "Select image number: " IMAGE_NUMBER; \
99-
IMAGE_SELECTED=$$(echo "$(IMAGES)" | tr ' ' '\n' | sort | sed -n "$${IMAGE_NUMBER}p"); \
100-
echo ""; \
101-
echo "Using features: $$FEATURES_SELECTED"; \
102-
echo "Using image: $$IMAGE_SELECTED"; \
103-
$(MAKE) devcontainer-up FEATURES="$$FEATURES_SELECTED" IMAGE="$$IMAGE_SELECTED"
138+
container-id:
139+
@docker ps --filter "name=devcontainer" --format "{{.ID}}"

README.md

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@ Modular, portable, and compatible with Amazon Linux, Ubuntu, and Debian-based sy
77
88
## Features Included
99

10-
- **Shell Environment** (Zsh, Oh My Zsh, Powerlevel10k, syntax highlighting, autosuggestions)
10+
- **Shell Environment** – Fully customizable Zsh setup with:
11+
12+
- Zsh and Oh My Zsh
13+
- Powerlevel10k theme
14+
- Zsh autosuggestions and syntax highlighting
15+
- Nerd Font v3 (Meslo)
16+
- Timezone configuration
17+
- Optional opinionated configuration
18+
- Override support via `.zshrc` and `.p10k.zsh` URLs
19+
- Post-install script hook
20+
1121
- **AWS CLI v2**
1222
- **Terraform** (with tfswitch)
1323
- **OpenTofu** (Terraform fork)
1424
- **Python** (via pyenv, with optional pipenv)
1525
- **Node.js** (via nvm)
16-
- **Pre-commit** (Hooks Setup)
26+
- **Pre-commit** (Hooks setup)
1727

1828
Each tool is packaged as an independent, composable DevContainer Feature.
1929

@@ -23,20 +33,34 @@ You can reference features from this repository directly using the `gh:` prefix
2333

2434
```json
2535
{
26-
"features": {
27-
"gh:jonmatum/devcontainer-features/python:1.0.0": {
28-
"version": "3.11.9",
29-
"pipenv": true
30-
},
31-
"gh:jonmatum/devcontainer-features/aws:1.0.0": {},
32-
"gh:jonmatum/devcontainer-features/terraform:1.0.0": {},
33-
"gh:jonmatum/devcontainer-features/shell:1.0.0": {}
36+
"features": {
37+
"gh:jonmatum/devcontainer-features/python:1.0.0": {
38+
"version": "3.11.9",
39+
"pipenv": true
40+
},
41+
"gh:jonmatum/devcontainer-features/aws:1.0.0": {},
42+
"gh:jonmatum/devcontainer-features/terraform:1.0.0": {},
43+
"gh:jonmatum/devcontainer-features/shell:1.1.0": {
44+
"timezone": "America/New_York",
45+
"opinionated": true,
46+
"zshrcUrl": "https://example.com/.zshrc",
47+
"p10kUrl": "https://example.com/.p10k.zsh"
3448
}
49+
}
3550
}
3651
```
3752

3853
Replace the feature ID and version according to your requirements.
3954

55+
## Feature Highlights
56+
57+
Each DevContainer Feature is:
58+
59+
- Composable – Add only the tools you need
60+
- Opinionated, but overridable – Defaults provided, but easy to customize
61+
- Reusable – Designed for local dev, CI/CD, and cloud workspaces
62+
- Tested – Verified against multiple base images and distros
63+
4064
## Structure
4165

4266
Each Feature is organized under the `src/` folder following the DevContainer [Feature distribution specification](https://containers.dev/implementors/features-distribution/).
@@ -46,6 +70,7 @@ src/
4670
shell/
4771
devcontainer-feature.json
4872
install.sh
73+
NOTES.md
4974
aws/
5075
devcontainer-feature.json
5176
install.sh
@@ -65,15 +90,13 @@ src/
6590

6691
## Versioning
6792

68-
Each Feature is individually versioned using the `version` attribute in its `devcontainer-feature.json`.
93+
Each Feature is individually versioned using the `version` attribute in its `devcontainer-feature.json`.
6994
Versioning follows [Semantic Versioning (SemVer)](https://semver.org/).
7095

71-
Example snippet from a Feature:
72-
7396
```json
7497
{
7598
"id": "shell",
76-
"version": "1.0.0",
99+
"version": "1.1.0",
77100
...
78101
}
79102
```
@@ -82,23 +105,31 @@ Releases are automated via GitHub Actions using [release-please](https://github.
82105

83106
## Publishing
84107

85-
Features are automatically published to **GitHub Container Registry (GHCR)** following the [DevContainer Feature distribution spec](https://containers.dev/implementors/features-distribution/).
108+
Features are automatically published to GitHub Container Registry (GHCR) following the [DevContainer Feature distribution spec](https://containers.dev/implementors/features-distribution/).
86109

87110
- Each Feature is published individually under:
88111
`ghcr.io/jonmatum/devcontainer-features/<feature>:<version>`
89-
- A **feature collection metadata package** is also published:
112+
113+
- A collection metadata package is also published:
90114
`ghcr.io/jonmatum/devcontainer-features`
91115

92-
> **Important:** After publishing, Features must be manually marked as **Public** in the GitHub Package settings to be discoverable and usable.
116+
Important: After publishing, Features must be manually marked as **Public** in the GitHub Package settings to be discoverable and usable.
93117

94118
**Example URLs:**
95-
- Feature: `https://github.com/users/jonmatum/packages/container/devcontainer-features/shell`
96-
- Collection: `https://github.com/users/jonmatum/packages/container/devcontainer-features`
119+
120+
- Feature: [Shell Feature Package](https://github.com/users/jonmatum/packages/container/devcontainer-features/shell)
121+
- Collection: [Feature Collection](https://github.com/users/jonmatum/packages/container/devcontainer-features)
97122

98123
## License
99124

100-
Licensed under the [MIT License](LICENSE).
125+
This project is [MIT License](./LICENSE).
126+
Originally scaffolded from Microsoft’s Dev Container Feature starter kit.
127+
Extended and maintained by Jonatan Mata, 2025.
128+
129+
## Additional Resources
130+
131+
For advanced configuration, usage examples, and feature-specific notes, refer to the `NOTES.md` file within each feature’s folder.
101132

102133
---
103134

104-
> echo "Pura Vida & Happy Coding!";
135+
> echo "Pura Vida & Happy Coding!";

0 commit comments

Comments
 (0)