# Building, Testing, and Contributing

## Testing Latest Commits on HEAD

There are several ways to use DDEV’s latest-committed HEAD version:

- **[`ddev utility download-ddev`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#utility-download-ddev>)**: Run `ddev utility download-ddev --head` to download the latest `main` build into `~/tmp/ddev-download-ddev/main`. It prints how to use the new binary in your current shell.
- **Download** the latest `main` branch artifacts from [nightly.link](<https://nightly.link/ddev/ddev/workflows/main-build/main>). Each of these is built by the CI system, signed, and notarized. Get the one you need and place it in your `$PATH`.
- **Homebrew install HEAD**: On macOS and Linux, run `brew unlink ddev && brew install ddev/ddev/ddev --HEAD --fetch-HEAD` to get the latest DDEV commit, even if it’s unreleased.
- **Install via script**: You can download and run the [install\_ddev\_head.sh](<https://raw.githubusercontent.com/ddev/ddev/main/scripts/install_ddev_head.sh>) script, or run it automatically:

  ```
  # Download and run the install script
  curl -fsSL https://raw.githubusercontent.com/ddev/ddev/main/scripts/install_ddev_head.sh | bash
  ```
- **Build manually**: If you have normal build tools like `make` and `go` installed, you can check out the code and run `make`.

## Testing a PR

Each [PR build](<https://github.com/ddev/ddev/actions/workflows/pr-build.yml>) creates GitHub artifacts you can use for testing. The easiest way is [`ddev utility download-ddev`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#utility-download-ddev>), described first below; you can also download the artifact you need from the PR page manually and install it yourself.

> [!TIP]
>
> **You can also [downgrade to an older version of DDEV](<https://ddev.github.io/ddev/en/stable/users/usage/faq/#how-can-i-install-a-specific-version-of-ddev>) (perform a rollback).**

### Downloading a build with `ddev utility download-ddev`

[`ddev utility download-ddev`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#utility-download-ddev>) downloads the `ddev` and `ddev-hostname` binaries for a build and prints how to use them in your current shell:

```
# Download this PR's build for your current OS and architecture into ~/tmp/ddev-download-ddev/pr-<PR_NUMBER>
ddev utility download-ddev --pr <PR_NUMBER>
# See the help for more options:
ddev utility download-ddev --help
```

It can also fetch builds by branch, commit, release tag, or `main` HEAD, and cross-download other platforms.

### Downloading a build manually

Normally, you can put any executable in your path, and it takes precedence, so you don’t need to remove or disable an already installed DDEV instance, which we will use here. This example uses `~/bin`. Since not every OS has `$HOME/bin` in `$PATH`, you can create the folder and add it to your path by updating `~/.bashrc`, `~/.zshrc`, or another relevant shell configuration file with these commands:

```
mkdir ~/bin
echo 'export PATH="$HOME/bin:$PATH"' >>~/.bashrc
source ~/.bashrc
# Verify that `$HOME/bin` is the first entry in your `$PATH`
echo $PATH
```

Download a ZIP file for your OS and architecture by clicking the link (the result is something like `ddev-macos-arm64.zip`) or using `wget`, `curl`, `dl` and unzip it, make it executable, and move it to the `~/bin` folder:

```
# Example for macOS Apple Silicon:
unzip ddev-macos-arm64.zip
chmod +x ddev && mv ddev ~/bin/ddev
rm -f ddev-macos-arm64.zip
```

```
# Example for macOS Intel:
unzip ddev-macos-amd64.zip
chmod +x ddev && mv ddev ~/bin/ddev
rm -f ddev-macos-amd64.zip
```

```
# Example for Linux and WSL2:
unzip ddev-linux-amd64.zip
chmod +x ddev && mv ddev ~/bin/ddev
rm -f ddev-linux-amd64.zip
```

```
# Example for Linux and WSL2 ARM64:
unzip ddev-linux-arm64.zip
chmod +x ddev && mv ddev ~/bin/ddev
rm -f ddev-linux-arm64.zip
```

![Github Action PR Comment ZIP files](<https://ddev.github.io/ddev/en/stable/images/github-action-pr-comment.png>)

Tip: If you need a zip-file to try out the “Testing a PR” process, see the [nightly builds](<https://nightly.link/ddev/ddev/workflows/main-build/main>).

> [!WARNING]
>
> **macOS and Unsigned Binaries (click me)**
>
> macOS doesn’t like these downloaded binaries, so you’ll need to bypass the automatic quarantine to use them:
>
> ```
> xattr -r -d com.apple.quarantine ~/bin/ddev
> ```
>
> (The binaries on the `main` branch and the final release binaries *are* signed.)

Verify the replacement worked by running `ddev -v`. The output should be something like `ddev version v1.23.5-98-g3c93ae87e`, instead of the regular `ddev version v1.23.5`. Valuable commands for debugging are `which -a ddev` and `echo $PATH`.

When DDEV detects a version change, it recommends [powering down](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#poweroff>) all running containers. Then, it will download the new images, if required.

After you’re done testing, you can delete your downloaded executable, restart your terminal, and again use the standard DDEV:

```
rm ~/bin/ddev
```

## Making Changes to DDEV Images

If you need to make a change to one of the DDEV images, just edit the files under that image’s directory (for example `containers/ddev-webserver/Dockerfile`), then build from the repository root:

```
make
```

`make` detects that the image’s own content changed, builds it locally for your host architecture, and automatically rewrites the matching tag (like `WebTag`) in `pkg/versionconstants/versionconstants.go` — there’s no tag to invent and no file to hand-edit. If nothing under `containers/` changed, `make` does nothing extra: no Docker calls, no network access.

This also applies after a rebase or merge — if a base image or shared file changed upstream, `make` detects the mismatch and rebuilds the affected image the same way, even if you didn’t edit anything yourself.

External package repositories are not part of an image’s content hash. To rebuild `ddev-webserver` against updated external dependencies, bump `EXTERNAL_DEPENDENCIES_REFRESH` in its `Dockerfile`, then run `make`. This invalidates the base package-install layer and gives the rebuilt image a new tag.

`ddev version` should show you that you are using the freshly built tag, and [`ddev start`](<https://ddev.github.io/ddev/en/stable/users/usage/commands/#start>) will use the local image without pulling.

Run `make print-image-tags` at any time to see the tag each image would currently use, whether or not it’s actually been built yet — this is the value to use with the GitHub Actions push workflows described below if you need the image pushed to the registry.

`containers/ddev-dbserver` is a special case: one tag (`BaseDBTag`) covers many db type/version combinations, so `make` only builds the default variant (currently `mariadb_11.8`) locally. To build a different db type/version after changing `containers/ddev-dbserver`, build it manually using the tag `make print-image-tags` reports:

```
cd containers/ddev-dbserver
make mysql_8.0_amd64 VERSION=<tag-from-print-image-tags>
```

## Docker Image Changes

Running `make` at the repository root (above) already builds a changed image locally and updates `pkg/versionconstants/versionconstants.go` for you. To make that same build available to CI or other developers, it also needs to be pushed to the registry:

- Get the exact tag `make` used, with `make print-image-tags` (or read it straight out of `pkg/versionconstants/versionconstants.go` after running `make`).
- Push an image with that tag by navigating to the image directory (like `containers/ddev-webserver`), and running `make push DOCKER_REPO=youruser/yourimage VERSION=<tag>`.
- Multi-arch images require you to have a Buildx builder, so `docker buildx use multi-arch-builder || docker buildx create --name multi-arch-builder --use`.
- You can’t push until you `docker login`.
- Push a container to hub.docker.com with that same tag. Push to `<yourorg>/ddev-webserver` repository with `make push DOCKER_ORG=<yourorg> VERSION=<tag>` **in the container directory**. You might have to use other techniques to push to another repository.

### Local Builds and Pushes

To use `buildx` successfully you have to have the [`buildx` Docker plugin](<https://docs.docker.com/buildx/working-with-buildx/>), which is in many environments by default.

To build multi-platform images you must `docker buildx use multi-arch-builder || docker buildx create --name multi-arch-builder --use` as a one-time initialization.

- Running `make` from the repository root already does a quick local build of any changed image for your host architecture (see [Making Changes to DDEV Images](<#making-changes-to-ddev-images>) above). To build a specific image or db type/version directly instead, you can still:

  - `cd containers/<image>` then `make VERSION=<version>`
  - for `ddev-dbserver`: `make mariadb_10.3 VERSION=<version>` etc.
- To push manually:

```
cd containers/ddev-webserver
make push VERSION=<tag>
```

If you’re pushing to a repository other than the one wired into the Makefile (like `ddev/ddev-webserver`):

```
cd containers/ddev-webserver
make push VERSION=<tag> DOCKER_REPO=your/dockerrepo
```

### Pushes Using GitHub Actions

Normally the [Image build](<https://github.com/ddev/ddev/actions/workflows/image-build-push.yml>) workflow (see [Automatic Image Build and Push](<#automatic-image-build-and-push>)) handles pushing a changed image automatically for any pull request, including forks. The workflows below are for manually pushing a specific tag — a re-push, or one of the `ddev-dbserver` variants other than the default `mariadb_11.8` that `make` auto-builds.

To manually push using GitHub Actions,

#### For Most Images

- Visit [Actions → Push tagged image](<https://github.com/ddev/ddev/actions/workflows/push-tagged-image.yml>)
- Click “Run workflow” in the blue band near the top.
- Choose the branch, usually `main` and then the image to be pushed, `ddev-webserver`, `ddev-dbserver`, etc.

#### For `ddev-dbserver`

- Visit [Actions → Push tagged db image](<https://github.com/ddev/ddev/actions/workflows/push-tagged-dbimage.yml>)
- Click “Run workflow” in the blue band near the top.
- Choose the branch, usually `main`. Include a tag for the pushed image and GitHub will do all the work.

## Instrumentation

The [instrumentation](<https://ddev.github.io/ddev/en/stable/users/usage/diagnostics/index.md>) implementation is generated using the [Ampli Codegen](<https://www.docs.developers.amplitude.com/data/sdks/go/ampli/>).

To synchronize the implementation with the latest changes at Amplitude, the CLI tool has to be installed locally:

```
npm install -g @amplitude/ampli
```

Make changes to the event definition using the GUI at [https://data.amplitude.com/ddev/DDEV](<https://data.amplitude.com/ddev/DDEV>):

- create a new branch
- create or change events and properties
- save changes to the new branch
- update the implementation with `ampli checkout <branch name>`
- make changes to the code

Once finished, save the changes to publish a new version of the definitions.

Afterwards the changes can be imported running the following command in the project root:

```
ampli pull
```

Once the changes are ready to be merged, merge the changes made in the new branch to the `main` branch in the Amplitude backend (the `Merge` button is on the `Activity` tab on the left) and switch back to the `main` branch:

```
ampli checkout main
```

Make sure the API keys are not included to the sources; they are linked during compilation using a GitHub secret.

### Environments

There are two environments defined, `DDEV - Production` and `DDEV - Development`. Master builds will deliver the data to production, PR builds to development.

When working on Amplitude, please always make sure the correct environment is selected or you won’t see any data. Selection is possible on most pages.

### User and event data

The first step is always to identify the device, this includes data like OS, architecture, DDEV version, Docker, etc., details are visible in the *User Properties*. The devices are called *Users* in the Amplitude backend. So every user represents an unique device on which DDEV is installed.

The second step is to collect data about the command which was called by the user and is delivered by a dedicated `Command` event.

The `Project` event finally collects data about the loaded project(s) which includes important configuration details like PHP version, database, etc.

### Debugging

Information about data debugging can be found at [https://www.docs.developers.amplitude.com/data/debugger/](<https://www.docs.developers.amplitude.com/data/debugger/>). *Ingestion debugger* or via *User lookup* are the most useful options for DDEV.

Don’t forget to select the matching environment while debugging.

### Examining data on Amplitude.com

First, local `ddev` binaries have to be built with `AmplitudeAPIKey` set. Visit `https://app.amplitude.com/data/ddev/DDEV/sources/production` and select either “Production” or “Development”, then click the “Go SDK” line to get the API key. Then set `export AmplitudeAPIKey=<key>` and build the binaries with `make`.

Then run `ddev` commands as usual, and the data will be sent to Amplitude.

- You can examine data on the local side with `export DDEV_VERBOSE=true` but it’s awkward. However, the actual data is always marked with `AMPLITUDE:` and the `EventType` will be `Command`, `Project`, or `$identify` (User data). For example, DDEV\_VERBOSE=true ddev start 2\>&amp;1 | grep AMPLITUDE\`
- To see the data show up on Amplitude, you’ll need to `ddev utility instrumentation flush`.
- To make it easier to find your data, use the “Development” key and set your `instrumentation_user` to a familiar value in `$HOME/.ddev/global_config.yaml`. For example, `instrumentation_user: rfay` would make it so you can find the user `rfay`.
- To inspect data, visit “User Lookup”, (`https://app.amplitude.com/analytics/ddev/activity`) and choose the correct source in the upper left (“DDEV Production” or “DDEV Development”). Then use “Search users” in the upper right to find the user you are studying. If you’ve used an `instrumentation_user` it will be searchable as “User”. (Advanced-\>where: “User” = “rfay”. for example). You’ll then have a page devoted to the events of that user.

## Building

- You’ll want both your fork/branch and the upstream as remotes in Git, so that tags can be determined. For example, the upstream Git remote can be `https://github.com/ddev/ddev` and your fork’s remote can be `git@github.com:<yourgithubuser>/ddev`. Without the upstream, Git may not know about tags that it needs for tests to work.
- To run tests, you’ll want `~/tmp` to be allowed in Docker. This is not normally an issue as the home directory is available by default in most Docker providers.

Build the project with `make` and your resulting executable will end up in `.gotmp/bin/linux_amd64/ddev` or `.gotmp/bin/linux_arm64/ddev` (for Linux) or `.gotmp/bin/windows_amd64/ddev.exe` or `.gotmp/bin/windows_arm64/ddev.exe` (for Windows) or `.gotmp/bin/darwin_amd64/ddev` or `.gotmp/bin/darwin_arm64/ddev` (for macOS).

You can add additional `go build` args with `make BUILDARGS=<something>`, for example, `make BUILDARGS=-race`.

Build/test/check static analysis with:

```
make # Builds on current os/architecture
make BUILDARGS=-race
make linux_amd64
make linux_arm64
make darwin_amd64
make darwin_arm64
make windows_amd64
make windows_arm64
make test
make clean
make staticrequired
```

## Testing

Normal test invocation is `make test`. Run a single test with an invocation like `go test -v -run TestDevAddSites ./pkg/...` or `make test TESTARGS="-run TestDevAddSites"`. The easiest way to run tests is from inside the excellent Go IDE [GoLand](<https://www.jetbrains.com/go/>). Click the arrowhead to the left of the test name. This is also easy to do in Visual Studio Code.

To test with race detection, `make test TESTARGS="-race"` for example.

To see which DDEV commands the tests are executing, set the environment variable `DDEV_DEBUG=true`.

Use `GOTEST_SHORT=true` to run one CMS in each test, or `GOTEST_SHORT=<integer>` to run exactly one project type from the list of project types in the [TestSites array](<https://github.com/ddev/ddev/blob/main/pkg/ddevapp/ddevapp_test.go#L43>). For example, `GOTEST_SHORT=5 make test TESTARGS="-run TestDdevFullSiteSetup"` will run only `TestDdevFullSiteSetup` against TYPO3.

To run a test (in the `cmd` package) against a individually-compiled DDEV binary, set the `DDEV_BINARY_FULLPATH` environment variable, for example `DDEV_BINARY_FULLPATH=$PWD/.gotmp/bin/linux_amd64/ddev make testcmd`.

To run all tests in a specific package, use `make testonepkg TESTPKG=./pkg/dockerutil`. Combine with `TESTARGS` to filter further, for example `make testonepkg TESTPKG=./pkg/dockerutil TESTARGS="-run TestDockerUtils"`.

To run all tests defined in a single `_test.go` file, use `make testonefile TESTFILE=./cmd/ddev/cmd/utility-tls-diagnose_test.go`. This automatically extracts the test function names from the file and builds the `-run` pattern.

When triggering a custom Buildkite or GitHub Actions run, the same targets are available via environment variables: set `MAKE_TARGET=testonepkg` with `TESTPKG=./pkg/dockerutil`, or `MAKE_TARGET=testonefile` with `TESTFILE=./cmd/ddev/cmd/utility-tls-diagnose_test.go`.

The easiest way to run tests is using GoLand (or VS Code) with their built-in test runners and debuggers. You can step through a specific test; you can stop at the point before the failure and experiment with the site that the test has set up.

## Automated Testing

Anybody can view the CircleCI automated tests, and they usually show up any problems that are not OS-specific. Click through on the testing section of the PR to see them.

The Buildkite automated tests require special access, which we typically grant to any PR contributor that asks for it.

## Docker Image Development

The Docker images that DDEV uses are included in the `containers/` directory:

- `containers/ddev-webserver` provides the web servers for per-project `web` containers.
- `containers/ddev-dbserver` provides the `db` container for per-project databases.
- `containers/ddev-ssh-agent` provides a single in-Docker-network SSH agent so projects can use your SSH keys.
- `containers/ddev-traefik-router` is the current Traefik-based router image.
- `containers/ddev-xhgui` provides a web interface to analyze performance profiles generated by xhprof.

When you change an image, running `make` from the repository root builds it locally and computes/updates its tag in `pkg/versionconstants/versionconstants.go` automatically — no manual tag-inventing or file-editing needed, and this works for any contributor, including from a fork. If you’d rather leave the build to CI, `make retag-images` writes the same tags without running Docker at all; the tags it writes then name images that exist nowhere until the pull request’s push completes, so `ddev start` won’t work until then. Once you push that commit as a pull request, the [Image build](<https://github.com/ddev/ddev/actions/workflows/image-build-push.yml>) workflow detects the changed image and builds and pushes it to the registry automatically — no maintainer needs to run anything by hand. See [Automatic Image Build and Push](<#automatic-image-build-and-push>) below for when that requires a maintainer’s approval and when it doesn’t.

### Automatic Image Build and Push

An image’s tag is the bare content hash of the files it’s built from — `ddev/ddev-webserver:36bceca65e`, with no branch prefix. The same content therefore resolves to the same tag no matter which branch, fork, or Docker Hub organization published it, which is what lets `make`, the CI detector, and every test runner agree without coordinating. `versionconstants.go` records the branch alongside it, both as a trailing comment and as a `WebTagBranch`-style variable that `ddev version` shows, and each push also publishes a readable `<branch>-<hash>` alias pointing at the same manifest.

Opening a pull request that touches `containers/` triggers the [Image build](<https://github.com/ddev/ddev/actions/workflows/image-build-push.yml>) workflow. A `detect` job always runs first: it recomputes each image’s content hash and checks whether that tag is already in the registry. If it is, there’s nothing to build — so a pull request that touches `containers/` without changing an image costs one registry lookup per image and no build.

The same resolution drives `containers/wait-for-images.sh`, which every test runner calls before pulling anything: it waits only for tags this commit will pull, and doesn’t wait at all for an image whose content changed, since `make` builds that one locally on the runner.

Changing `containers/ddev-dbserver` is the one case where `make` alone isn’t enough. Every database variant shares a single `BaseDBTag`, so a dbserver change moves the tag for all 20 of them at once, while `make` only builds the default `mariadb_11.8` locally — the other 19 can only come from the registry. CI builds and pushes the whole matrix (36 jobs, from `containers/ddev-dbserver/variants.txt`), so **run `make` and commit the `versionconstants.go` change**; `wait-for-images.sh` stops the test run with that instruction if you don’t.

What happens next depends on whether the PR is from a fork:

- **Fork PRs** (security boundary — the PR could contain an arbitrary Dockerfile/build script) **from someone without push access**: as soon as `detect` finds an image to build, an `approve` job asks a maintainer to sign off on the `image-push` environment, before anything is built. Once approved, a `build` job builds the image(s) per architecture with no registry credentials at all — nothing in that job can reach `docker.io`, so there’s nothing to gain by gating it after the fact instead of before. A separate, trusted `image-push.yml` workflow — which never checks out or runs the pull request’s code — then loads what `build` produced and pushes it, re-validating both the tag and every repository name first (`containers/validate-image-tag.sh`, `containers/validate-image-repo.sh`) so the earlier approval can only ever publish a hash-shaped tag under a known DDEV repository. A comment is posted on the PR once the push completes. If `detect` found nothing to build, no approval is requested at all.
- **Fork PRs from a maintainer’s own fork** (`author_association` is `OWNER`, `MEMBER`, or `COLLABORATOR`): the same fork-safe `build` / `image-push.yml` split runs, but the `approve` job is skipped — they already have push access, so there’s nothing to ask permission for.
- **Everything else** (a push to `main`, or a pull request from a branch in the same repository — no fork content is ever involved): `build-and-push` builds and pushes directly in one step, with no approval gate at all — the same trust level `main-build.yml` already runs at unguarded. A `create-manifests` job then assembles the multi-arch manifest and comments on the PR, if there is one.

So a maintainer only ever needs to click **Approve** once — as soon as a fork PR without push access changes a container image — and only that once; everything else is fully automatic.

### Cleaning Up Unused Image Tags

Every image change pushes new tags to Docker Hub, and most are only useful until the release that follows. `containers/image-tag-cleanup.sh` finds the tags nothing uses any more and, only when told to with `--execute`, deletes them. The [Image tag cleanup](<https://github.com/ddev/ddev/actions/workflows/image-tag-cleanup.yml>) workflow runs the same script.

#### What counts as unused

A tag is a candidate for deletion only when all of these hold:

- It has a shape that CI or a branch build produces: a bare hash, a `<branch>-<hash>` alias, a date-prefixed branch tag like `20250612_stasadev_rebuild_images`, or a leftover `-amd64`/`-arm64` tag. Release tags, `latest`, and anything unrecognized are never candidates.
- It isn’t named in a version file (`pkg/versionconstants/versionconstants.go`, or `pkg/version/version.go` for older releases) at any `v*` release tag, at the head of any open pull request, or on the main branch within the last 90 days (`--keep-main-branch-days`).
- It was pushed more than 90 days ago (`--older-than-days`) and hasn’t been pulled in the last 30 days (`--not-pulled-for-days`).
- No kept tag points at the same manifest.

A deleted tag that a pull request later needs is pushed again by that pull request’s next image build, so the worst case is one rebuild.

#### From the command line

Run these from a checkout of ddev/ddev with the release tags fetched (`git fetch upstream --tags`) and [`gh`](<https://cli.github.com/>) logged in. The default run deletes nothing and lists what it would:

```
containers/image-tag-cleanup.sh
```

To include tags that were pulled recently, or only recently pushed ones, change the two rules:

```
containers/image-tag-cleanup.sh --older-than-days 10 --not-pulled-for-days 0
```

To see why each tag was kept or listed, write the decisions to a file:

```
containers/image-tag-cleanup.sh --decisions ~/tmp/decisions.tsv
```

To delete, set the credentials and add `--execute`. It asks before deleting. `--limit` deletes only the first few candidates, which is the way to try it on a small batch first:

```
export DOCKERHUB_USERNAME=<user> DOCKERHUB_TOKEN=<token>
containers/image-tag-cleanup.sh --limit 5 --execute
```

Every flag can be written `--flag value` or `--flag=value`, and `--help` lists them all. `--max-delete` refuses lists longer than its value (default 1000), and `--docker-org` chooses the Docker Hub organization (default `ddev`). The three steps also run alone, each with its own `--help`: `image-tag-keep-set.sh` lists the tags to keep, `image-tag-cleanup-candidates.sh` lists the tags that can go, and `delete-image-tags.sh` deletes the tags it is given, after checking that each is still a candidate.

#### From GitHub

The workflow has two jobs. `report` runs every Monday, and on demand with no inputs. It needs no credentials and only lists candidates: the run summary shows the counts and the list, and the `image-tag-cleanup-report` artifact holds every tag’s decision in `decisions.tsv`.

`delete` runs only when started by hand from `main` with `tags` or `report_run_id`. The `image-tag-cleanup` environment must approve it, and it deletes only if `execute` is checked, so an unchecked run is a dry run:

1. Run the workflow with every input empty, and read the report.
2. Run it with `report_run_id` set to that run’s ID, `execute` unchecked, and approve. The log shows `would delete …` for each tag.
3. Run it again with the same `report_run_id` and `execute` checked, and approve. To delete only some tags, set `tags` to a list of `<org>/<repo>:<tag>` entries instead.

Before deleting, the job recomputes the candidates, and if any requested tag is no longer a candidate, it deletes nothing. The repository variables `CLEANUP_OLDER_THAN_DAYS` and `CLEANUP_NOT_PULLED_FOR_DAYS` change the two rules for the workflow, and `DOCKER_ORG` chooses the Docker Hub organization (ddev-test uses `ddevhq` on its own).

## Pull Requests

To contribute your fixes or improvements to DDEV, make a pull request on GitHub. If you’re undertaking a large change, create an issue first so it can be discussed before you invest a lot of time. When you’re ready, create a pull request, and a discussion will start around your proposed changes. Other contributors and users may chime in, but ultimately the decision is made by the maintainer(s). You may be asked to make some changes to your pull request. If so, add more commits to your branch and push them. They’ll automatically go into the existing pull request.

If your pull request is merged, great! If not, no sweat; it may not be what the project maintainer had in mind, or they were already working on it. This happens, so our recommendation is to take any feedback you’ve received and go forth and pull request again. Or create your own open source project.

### Preparing a pull request

- **[Fork](<https://docs.github.com/en/get-started/quickstart/contributing-to-projects>) the repository** and clone it locally. Connect your local to the original ‘upstream’ repository by adding it as a remote, and pull upstream changes often so you stay up to date and reduce the likelihood of conflicts when you submit your pull request. See more detailed instructions on [GitHub](<https://help.github.com/articles/syncing-a-fork>).
- **Create a [branch](<https://docs.github.com/en/get-started/quickstart/github-flow>)** for your edits. See below for DDEV’s conventions for branch names.
- **Be clear** about the problem and how someone can recreate it, or why your feature will help. Be equally clear about the steps you took to make your changes.
- **It’s best to test**. Run your changes against any existing tests and create new tests when needed. Whether tests exist or not, make sure your changes don’t break the existing project.

### Feature branch name

When preparing your pull request, please use a branch name like `YYYYMMDD_<your_username>_short_description` (like `20230901_rfay_short_description`) so it’s easy to identify you as the author.

### Pull Request Title Guidelines

We have very precise rules over how our PR titles (and thus main-branch commits) are to be formatted. This leads to **more readable messages** that are easy to follow when looking through the **project history**. But also, we use the main-branch Git commit messages to **generate the changelog** for the releases.

The pull request title must follow this convention which is based on the [Conventional Commits](<https://www.conventionalcommits.org/en/v1.0.0/>) specification:

```
<type>[optional scope][optional !]: <description>[, fixes #<issue>][, for #<issue>]
```

#### Examples

- `build: update Makefile logic`
- `chore(deps): bump mutagen to 0.18.1`
- `ci(pr): enforce commit message convention, fixes #5037`
- `docs: change code refs of Mac M1 to Apple Silicon`
- `feat: allow multiple upload dirs, fixes #4190, fixes #4796`
- `feat(pantheon): use environment variables`
- `fix: create upload_dir if it doesn't exist in ddev composer create-project, fixes #5031`
- `fix(auth): resolve password reset bug, for #1234`
- `refactor: add new Amplitude Property DDEV-Environment`
- `perf(image-pull): use docker-compose for parallel downloads`
- `style(readme): improve formatting`
- `test: optimize caching of downloaded assets`

#### Type

Must be one of the following:

- **build**: Changes that affect the build or external dependencies
- **chore**: Maintenance tasks, dependency updates, or other changes that don’t modify src or test files
- **ci**: Changes to our CI configuration files and scripts
- **docs**: Documentation only changes
- **feat**: A new feature
- **fix**: A bugfix
- **perf**: A code change that improves performance
- **refactor**: A code change that neither fixes a bug nor adds a feature
- **style**: Changes that do not affect the meaning of the code (whitespace, formatting, missing semi-colons, etc)
- **test**: Adding missing tests or correcting existing tests

Regarding SemVer, all types above except `feat` increase the patch version, `feat` increases the minor version.

#### Scope

An optional scope may be provided to add contextual information and is contained within parentheses, e.g., `feat(pantheon): add file uploads`. The scope should be a noun describing a section of the codebase surrounded by parentheses.

#### Breaking Changes

Breaking changes must have a `!` appended after type/scope.

Regarding SemVer, breaking changes increase the major version.

#### Subject / Description

The subject contains a succinct description of the change:

- use the imperative, present tense: “change” not “changed” nor “changes”
- don’t capitalize the first letter
- no dot (.) at the end

If an issue exists for the change, `, fixes #<issue number>` must be appended to the subject.

#### Revert

If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.

## Coding Style

Unless explicitly stated, we follow all coding guidelines from the Go community. While some of these standards may seem arbitrary, they somehow seem to result in a solid, consistent codebase.

It is possible that the codebase does not currently comply with these guidelines. We are not looking for a massive PR that fixes this since that goes against the spirit of the guidelines. All new contributions should make a best effort to clean up and make the codebase better than they left it. Obviously, apply your best judgment. Remember, the goal here is to make the codebase easier for humans to navigate and understand. Always keep that in mind when nudging others to comply.

Use `make staticrequired` to ensure that your code can pass the required static analysis tests.

The rules:

1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of [`golint`](<https://github.com/golang/lint>).
3. All code should follow the guidelines covered in [Effective Go](<http://golang.org/doc/effective_go.html>) and [Go Code Review Comments](<https://github.com/golang/go/wiki/CodeReviewComments>).
4. Comment the code. Tell us the why, the history and the context.
5. Document *all* declarations and methods, even private ones. Declare expectations, caveats and anything else that may be important. If a type gets exported, having the comments already there will ensure it’s ready.
6. Variable name length should be proportional to its context and no longer. `noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`. In practice, short methods will have short variable names and globals will have longer names.
7. No underscores in package names. If you need a compound name, step back, and re-examine why you need a compound name. If you still think you need a compound name, lose the underscore.
8. All tests should run with `go test` and outside tooling should not be required. No, we don’t need another unit testing framework. Assertion packages are acceptable if they provide *real* incremental value.
9. Even though we call these “rules” above, they are guidelines. Since you’ve read all the rules, you now know that.

If you are having trouble getting into the mood of idiomatic Go, we recommend reading through [Effective Go](<https://golang.org/doc/effective_go.html>). The [Go Blog](<https://blog.golang.org>) is also a great resource. Drinking the kool-aid is a lot easier than going thirsty.

## Contributor Live Training

We’re actively trying to increase the DDEV community of contributors and maintainers. To do that, we regularly do contributor training, and we’d love to have you come. The trainings are recorded for everybody’s benefit. The recordings and upcoming session dates can be found here: [DDEV Contributor Live Training](<https://ddev.com/blog/contributor-training/>).
