**Skips when:**`DEV_FTP_SUFFIX` variable is not set, or the branch starts with `chore/`.
---
## Configuration
### Organization Variables (required)
Configure at the **organization** level in **Settings → Secrets and variables → Actions → Variables**:
| Variable | Example | Description |
|----------|---------|-------------|
| `DEV_FTP_HOST` | `dev.example.com` | Dev server hostname. May include an explicit port suffix — `dev.example.com:2222`. |
| `DEV_FTP_PATH` | `/var/www/html` | Base remote path where files are deployed. |
| `DEV_FTP_USERNAME` | `deployuser` | SFTP username for authentication. |
### Organization Variable (optional)
| Variable | Example | Description |
|----------|---------|-------------|
| `DEV_FTP_PORT` | `2222` | Explicit port override. See **Port resolution** below. |
### Repository Variable (optional)
| Variable | Example | Description |
|----------|---------|-------------|
| `DEV_FTP_SUFFIX` | `my-module` | Appended to `DEV_FTP_PATH` to form the final deploy path: `DEV_FTP_PATH/DEV_FTP_SUFFIX`. Useful for deploying multiple repos to the same server. |
### Organization Secrets (credentials)
At least one of the following must be set:
| Secret | Description |
|--------|-------------|
| `DEV_FTP_KEY` | **Preferred.** SSH private key (any format supported by OpenSSH). |
| `DEV_FTP_PASSWORD` | SFTP password. Also used as the key passphrase when set alongside `DEV_FTP_KEY`. |
---
## Authentication logic
The workflow determines the connection method at runtime:
| Secrets present | Behaviour |
|-----------------|-----------|
| `DEV_FTP_KEY` + `DEV_FTP_PASSWORD` | Key auth with `DEV_FTP_PASSWORD` as the key passphrase. If key auth fails, retries with `DEV_FTP_PASSWORD` alone as an SFTP password. |
| `DEV_FTP_KEY` only | Key auth (no passphrase). Fails hard on auth error — no password fallback. |
| `DEV_FTP_PASSWORD` only | Password auth directly. |
| Neither | Workflow fails with an error message. |
---
## Port resolution
The SFTP port is determined in the following order — the first match wins:
2.**Port suffix in `DEV_FTP_HOST`** — if the host value contains `:`, the suffix is extracted and the bare hostname is used (e.g. `dev.example.com:2222` → host `dev.example.com`, port `2222`).
3.**Default** — port **22** is assumed when neither of the above is present.
---
## Remote path
The final remote path is constructed as:
```
DEV_FTP_PATH/DEV_FTP_SUFFIX
```
`DEV_FTP_SUFFIX` is optional. When set, exactly one `/` is inserted between the base path and the value regardless of trailing/leading slashes in the values.
---
## Manual dispatch
1. Go to **Actions → Deploy to Dev Server (SFTP)**.
2. Click **Run workflow**.
3. Optionally override the source directory (default: `src`) or enable **Dry run** to list files without uploading.
---
## Repository health checks
The `check_repo_health.php` script scores deployment readiness as part of the overall health report. When `--repo owner/repo` is supplied, it also calls the GitHub API to verify secrets and variables are configured.
Only org/repo administrators and maintainers may deploy. Contact your org administrator.
### No credentials configured
```
❌ No SFTP credentials configured.
Set DEV_FTP_KEY (preferred) or DEV_FTP_PASSWORD as an org-level secret.
```
Set at least one of `DEV_FTP_KEY` or `DEV_FTP_PASSWORD` in **Org Settings → Secrets**.
### Key authentication failed, no fallback
```
RuntimeError: Key authentication failed and no password fallback is available: ...
```
The SSH private key was rejected and `DEV_FTP_PASSWORD` is not set. Check the key is correct and authorized on the server, or add `DEV_FTP_PASSWORD` as a fallback.
### Missing source directory
```
⚠️ Source directory 'src' not found — skipping deployment
```
Expected behaviour for repos without a `src/` directory. No files are uploaded; the workflow exits successfully.
### Connection refused
```
[Errno 111] Connection refused
```
- Verify `DEV_FTP_HOST` is correct.
- Check the resolved port (shown in the **Resolve SFTP host and port** step log).
- Confirm the server firewall allows inbound SFTP on that port.