Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8ff2347
fix: switch freeform DDEV router to port 8080, fix coder-routes and d…
rfay May 5, 2026
4c8da02
test: add two-PHP-project routing test for freeform template
rfay May 5, 2026
700102b
fix: use cd into project dir for ddev delete in freeform cleanup
rfay May 5, 2026
ba0f835
fix: drop bash -c wrappers in freeform verify and cleanup steps
rfay May 5, 2026
39c549b
fix: run launch script directly to prevent DDEV overriding DDEV_SITENAME
rfay May 5, 2026
0e594fa
fix: use scp-script pattern for freeform verify steps
rfay May 5, 2026
dd53993
refactor: ship freeform test scripts alongside template
rfay May 5, 2026
951cc24
fix: detect primary web by slug, filter foreign-project services
rfay May 5, 2026
9a51fe8
ci: inject current-branch DDEV scripts into workspace before freeform…
rfay May 5, 2026
d40612a
fix: write docker-compose.coder-describe.yaml, improve WELCOME.txt, s…
rfay May 5, 2026
3e1cc6b
fix: add #ddev-silent-no-warn to host commands and config.coder.yaml
rfay May 5, 2026
3394006
fix: use WORKSPACE slug in primary web URL, run ddev config global af…
rfay May 5, 2026
b5d6cb1
test: fix freeform verify — expect WORKSPACE-based URL, check Coder U…
rfay May 5, 2026
6082a17
fix: use set -euo pipefail in startup script, add #ddev-silent-no-war…
rfay May 5, 2026
3fb3f22
fix: show Coder URLs in ddev describe via coder-url custom service
rfay May 5, 2026
cbb4202
feat: multi-project support in freeform template via for_each coder_app
rfay May 5, 2026
b3f3726
test: fix freeform CI — register project names at workspace creation,…
rfay May 5, 2026
e4e16b6
fix: show Web URL first, Mailpit second, other services last in ddev …
rfay May 5, 2026
8f1d2d9
fix: simplify docker-compose.coder-describe.yaml to use web service, …
rfay May 5, 2026
fede21a
test: verify both Web and Mailpit URLs in ddev launch and ddev describe
rfay May 5, 2026
4b73c7b
test: drop Mailpit URL checks, Web URL is sufficient
rfay May 5, 2026
f87b8e5
fix: give each project its own mailpit slug (mailpit-{project}) to av…
rfay May 5, 2026
566ae23
fix: write docker-compose.coder-describe.yaml in coder-setup, not cod…
rfay May 5, 2026
49cb88e
fix: give xhgui and adminer per-project slugs to avoid shared-port co…
rfay May 5, 2026
7ef01a1
run tests
rfay May 5, 2026
7ede5c8
ci: add GitHub Actions workflow to build and push Docker image
rfay May 5, 2026
add3b69
ci: include run_attempt in CI tag so retries don't conflict
rfay May 5, 2026
fd427ed
ci: shorten workspace names with ws_name matrix alias to stay under 3…
rfay May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/build-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Build and Push Docker Image

# Builds linux/amd64 on GitHub-hosted runners (native amd64) so arm64 dev
# machines don't need cross-compilation. Triggers on changes to the image or
# VERSION, and can be run manually from any branch for ad-hoc builds.
#
# Required repository secrets:
# DOCKERHUB_USERNAME - Docker Hub username
# DOCKERHUB_TOKEN - Docker Hub access token (read/write)

on:
push:
branches: [main]
paths:
- 'image/**'
- 'VERSION'
pull_request:
paths:
- 'image/**'
- 'VERSION'
workflow_dispatch:

jobs:
build:
name: Build image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Read version
id: version
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: image
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: |
ddev/coder-ddev:${{ steps.version.outputs.version }}
ddev/coder-ddev:latest
cache-from: type=gha
cache-to: type=gha,mode=max
93 changes: 82 additions & 11 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ jobs:
matrix:
include:
- template: user-defined-web
ws_name: udw
extra_vars: ""
extra_params: ""
app_slug: "ddev-web"
- template: freeform
ws_name: ff
extra_vars: ""
extra_params: ""
app_slug: ""
Expand All @@ -80,7 +82,8 @@ jobs:
run:
shell: bash -euo pipefail {0}
env:
WORKSPACE_NAME: ci-${{ matrix.template }}-${{ github.run_id }}
CI_TAG: ${{ github.run_id }}-${{ github.run_attempt }}
WORKSPACE_NAME: ci-${{ matrix.ws_name }}-${{ github.run_id }}-${{ github.run_attempt }}
CI: "true"
DDEV_NONINTERACTIVE: "true"
NO_COLOR: "1"
Expand Down Expand Up @@ -116,20 +119,35 @@ jobs:
coder templates push ${{ matrix.template }} \
--directory ${{ matrix.template }} \
--activate=false \
--name ci-${{ github.run_id }} \
--name ci-${{ env.CI_TAG }} \
--yes \
--variable workspace_image_registry=index.docker.io/ddev/coder-ddev \
${{ matrix.extra_vars }}

- name: Create workspace
if: ${{ matrix.template != 'freeform' }}
run: |
coder create ${{ env.WORKSPACE_NAME }} \
--template ${{ matrix.template }} \
--template-version ci-${{ github.run_id }} \
--template-version ci-${{ env.CI_TAG }} \
--parameter "vscode_extensions=[]" \
${{ matrix.extra_params }} \
--yes

- name: Create freeform workspace (with project names)
if: ${{ matrix.template == 'freeform' }}
run: |
cat > /tmp/freeform-params-${{ env.CI_TAG }}.yaml << EOF
project_names: "ci-site1-${{ env.CI_TAG }},ci-site2-${{ env.CI_TAG }}"
vscode_extensions: "[]"
EOF
coder create ${{ env.WORKSPACE_NAME }} \
--template ${{ matrix.template }} \
--template-version ci-${{ env.CI_TAG }} \
--rich-parameter-file /tmp/freeform-params-${{ env.CI_TAG }}.yaml \
--use-parameter-defaults \
--yes

- name: Verify workspace — agent connected
run: coder ssh ${{ env.WORKSPACE_NAME }} --wait=yes -- echo "Agent connected"

Expand All @@ -149,9 +167,9 @@ jobs:
if: ${{ matrix.app_slug != '' }}
run: |
# Write start script to runner-local file so we avoid the coder ssh heredoc+PTY hang
cat > /tmp/ci-ddev-start-${{ github.run_id }}.sh << 'EOF'
cat > /tmp/ci-ddev-start-${{ env.CI_TAG }}.sh << 'EOF'
set -euo pipefail
TESTDIR=/tmp/ci-ddev-${{ github.run_id }}
TESTDIR=/tmp/ci-ddev-${{ env.CI_TAG }}
echo "--- Creating test project in $TESTDIR ---"
mkdir -p "$TESTDIR/web" && cd "$TESTDIR"
ddev config --project-type=php --docroot=web
Expand All @@ -163,11 +181,11 @@ jobs:
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \
/tmp/ci-ddev-start-${{ github.run_id }}.sh \
coder@workspace:/tmp/ci-ddev-start-${{ github.run_id }}.sh
/tmp/ci-ddev-start-${{ env.CI_TAG }}.sh \
coder@workspace:/tmp/ci-ddev-start-${{ env.CI_TAG }}.sh
coder ssh ${{ env.WORKSPACE_NAME }} -- \
env CI=${{ env.CI }} DDEV_NONINTERACTIVE=${{ env.DDEV_NONINTERACTIVE }} NO_COLOR=${{ env.NO_COLOR }} \
bash /tmp/ci-ddev-start-${{ github.run_id }}.sh < /dev/null
bash /tmp/ci-ddev-start-${{ env.CI_TAG }}.sh < /dev/null

- name: Verify workspace — DDEV web externally accessible
if: ${{ matrix.app_slug != '' }}
Expand All @@ -183,8 +201,61 @@ jobs:
- name: Cleanup DDEV test project
if: ${{ matrix.app_slug != '' }}
run: |
coder ssh ${{ env.WORKSPACE_NAME }} -- ddev delete ci-ddev-${{ github.run_id }} --omit-snapshot -y < /dev/null || true
coder ssh ${{ env.WORKSPACE_NAME }} -- rm -rf /tmp/ci-ddev-${{ github.run_id }} < /dev/null || true
coder ssh ${{ env.WORKSPACE_NAME }} -- ddev delete ci-ddev-${{ env.CI_TAG }} --omit-snapshot -y < /dev/null || true
coder ssh ${{ env.WORKSPACE_NAME }} -- rm -rf /tmp/ci-ddev-${{ env.CI_TAG }} < /dev/null || true

- name: Inject current-branch DDEV scripts into freeform workspace
if: ${{ matrix.template == 'freeform' }}
run: |
coder ssh ${{ env.WORKSPACE_NAME }} -- mkdir -p /home/coder/.ddev/commands/host < /dev/null
for script in coder-routes coder-setup launch; do
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \
"image/scripts/.ddev/commands/host/${script}" \
"coder@workspace:/home/coder/.ddev/commands/host/${script}"
done
coder ssh ${{ env.WORKSPACE_NAME }} -- chmod +x /home/coder/.ddev/commands/host/coder-routes /home/coder/.ddev/commands/host/coder-setup /home/coder/.ddev/commands/host/launch < /dev/null

- name: Start two PHP projects — freeform routing test
if: ${{ matrix.template == 'freeform' }}
run: |
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \
freeform/scripts/test-freeform-start.sh \
coder@workspace:/tmp/test-freeform-start.sh
coder ssh ${{ env.WORKSPACE_NAME }} -- \
env CI=${{ env.CI }} DDEV_NONINTERACTIVE=${{ env.DDEV_NONINTERACTIVE }} NO_COLOR=${{ env.NO_COLOR }} \
bash /tmp/test-freeform-start.sh "${{ env.CI_TAG }}" < /dev/null

- name: Verify freeform — ddev launch and describe per-project URLs
if: ${{ matrix.template == 'freeform' }}
run: |
CODER_DOMAIN="${{ vars.TEST_CODER_URL }}"
CODER_DOMAIN="${CODER_DOMAIN#https://}"
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \
freeform/scripts/test-freeform-verify.sh \
coder@workspace:/tmp/test-freeform-verify.sh
coder ssh ${{ env.WORKSPACE_NAME }} -- \
env DDEV_NONINTERACTIVE=${{ env.DDEV_NONINTERACTIVE }} NO_COLOR=${{ env.NO_COLOR }} \
bash /tmp/test-freeform-verify.sh \
"${{ env.CI_TAG }}" "${{ env.WORKSPACE_NAME }}" "${OWNER}" "${CODER_DOMAIN}" \
< /dev/null

- name: Cleanup freeform PHP projects
if: ${{ always() && matrix.template == 'freeform' }}
run: |
coder show "${{ env.WORKSPACE_NAME }}" > /dev/null 2>&1 || {
echo "Workspace ${{ env.WORKSPACE_NAME }} not found — skipping DDEV cleanup"
exit 0
}
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \
freeform/scripts/test-freeform-cleanup.sh \
coder@workspace:/tmp/test-freeform-cleanup.sh
coder ssh ${{ env.WORKSPACE_NAME }} -- \
env DDEV_NONINTERACTIVE=${{ env.DDEV_NONINTERACTIVE }} \
bash /tmp/test-freeform-cleanup.sh "${{ env.CI_TAG }}" < /dev/null

- name: Delete workspace
if: always()
Expand All @@ -206,4 +277,4 @@ jobs:

- name: Archive CI template version
if: always()
run: coder templates versions archive ${{ matrix.template }} ci-${{ github.run_id }} --yes || true
run: coder templates versions archive ${{ matrix.template }} ci-${{ env.CI_TAG }} --yes || true
2 changes: 1 addition & 1 deletion .github/workflows/staging-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
extra_vars: ""
fail-fast: false
env:
VERSION_NAME: ci-${{ github.run_id }}
VERSION_NAME: ci-${{ github.run_id }}-${{ github.run_attempt }}

steps:
- uses: actions/checkout@v6
Expand Down
20 changes: 20 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@ This project provides a Coder v2+ template for DDEV-based development environmen

- Use `jq` (not `python3 -m json.tool`) for JSON pretty-printing and querying

## Working with Coder Workspaces via SSH

After running `coder config-ssh --yes`, workspaces are available as SSH hosts named `<workspace>.coder`. Use `scp` to copy files in or out, then `ssh` to execute scripts non-interactively:

```bash
# Configure SSH (once)
coder config-ssh --yes

# Copy a file into a workspace
scp ./local-file.sh mp1.coder:/tmp/

# Execute a script non-interactively (preferred — avoids PTY/pipe issues)
ssh mp1.coder bash /tmp/local-file.sh

# One-liner for quick commands
ssh mp1.coder ddev list
```

When running commands via `coder ssh -- ...` or piped heredocs, the PTY allocation causes interactive prompts and pipe-stall issues. Writing a script to `/tmp/` and executing it via `ssh workspace.coder bash /tmp/script.sh` is reliable for multi-step operations.

## Essential Commands

### Template Management
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ TEMPLATE_VARS_freeform := --variable workspace_image_registry=index.dock
TEMPLATE_EDIT_user-defined-web := --display-name "DDEV Web Workspace"
TEMPLATE_EDIT_drupal-core := --display-name "Drupal Core Development" \
--description "Drupal core dev environment: full DDEV stack, core clone, Umami demo site. Ready in about a minute."
TEMPLATE_EDIT_freeform := --display-name "DDEV Freeform (Traefik)"
TEMPLATE_EDIT_freeform := --display-name "DDEV Freeform (Traefik)" --default-ttl 24h

# Shared recipe for pushing any template (call with template name as argument)
define push_template
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.3
v0.4
Loading
Loading