deploy/
One entry per deployment target: local, ssh, ftp, docker, digitalocean.
deploy/
Each deploy/*.bx/.json entry describes one deployment target attempt - a place to ship a built/packaged project to:
// deploy/production.bx
class {
function configure() {
return {
target : "digitalocean",
appName : "my-agent",
region : "nyc",
registry : { type : "ghcr", repository : "myorg/my-agent" },
httpPort : 8080,
instanceSize : "apps-s-1vcpu-1gb",
envs : [ { key : "OPENAI_API_KEY", scope : "RUN_TIME", type : "SECRET" } ]
};
}
}
Run it with:
bxAgents deploy --name=production
deploy/ is deliberately not validated on every build - deployment config never affects the generated ColdBox app or its manifest, so checking it on every build would be pure overhead for projects that never deploy. It's only discovered and validated when deploy actually runs.
The simplest case needs no deploy/ folder at all:
bxAgents deploy --destination=/path/to/somewhere
is shorthand for the local target. Every other target needs more configuration than a couple of flags can reasonably carry, so it requires a named deploy/* entry (--name=<entry>).
Targets
Every target implements the same IDeploymentTarget interface (struct function deploy( config, context )) - the target field picks which one runs.
Copy the newest .bxa to a destination directory. No deploy/ folder needed.
Ship over scp, optionally restart the remote service over ssh.
Build an image from .build/app and push it to a registry.
Push-and-minimal-provision against the DigitalOcean App Platform API.
Ship the newest .bxa to a remote directory over plain FTP or SFTP.
local
Copies the newest packaged .bxa (by file modification time, never a lexical filename sort - v9.0.0 would otherwise sort after v10.0.0) to a destination directory.
{ target: "local", destination: "/path/to/somewhere" }
Requires a prior bxAgents package.
ssh
Ships the newest .bxa to a bare server over scp, then optionally runs a remote restart command over ssh.
{
target : "ssh",
host : "example.com",
username : "deploy",
remotePath : "/srv/apps/my-agent",
identityFile : "/home/me/.ssh/id_rsa", // optional
restartCommand : "systemctl restart my-agent" // optional
}
Requires a prior bxAgents package. Shells out to the real scp/ssh binaries - they must be installed and on PATH.
docker
Builds a Docker image from .build/app and pushes it to a container registry, shelling out to the real docker CLI.
{
target : "docker",
registry : { type : "ghcr", repository : "myorg/my-agent" }, // type: "dockerhub" | "ghcr" | "docr"
tag : "1.0.0" // optional, defaults to "latest"
}
Requires a prior bxAgents build (not package - it builds straight from .build/app). The built-in Dockerfile template is based on the real, published ortussolutions/boxlang:miniserver image - override it entirely by adding your own deploy/Dockerfile, or point at a different one via dockerfile: "/absolute/path".
docker login only runs when both DOCKER_USERNAME and DOCKER_PASSWORD are set in the environment - a registry an already-authenticated local Docker daemon can reach needs neither.
digitalocean
Deploys to a DigitalOcean App Platform app - "push and minimal provision": builds/pushes the image exactly like docker (reuses the same registry config shape), then either redeploys an existing app or creates one from scratch if it doesn't exist yet.
{
target : "digitalocean",
appName : "my-agent",
region : "nyc", // optional, defaults to "nyc"
registry : { type : "docr", repository : "myorg/my-agent" }, // "namespace/repo" form
httpPort : 8080, // optional, defaults to 8080
instanceSize : "apps-s-1vcpu-1gb", // optional
instanceCount : 1, // optional, defaults to 1
envs : [ { key : "OPENAI_API_KEY", scope : "RUN_TIME", type : "SECRET" } ] // optional
}
registry.repository must be in namespace/repo form (e.g. myorg/my-agent) - the DigitalOcean App Spec's image block needs the namespace and repository as separate fields. Requires a prior bxAgents build.
An existing app is found by name (GET /v2/apps, filtered client-side on spec.name) rather than remembering a local app-ID file, so this works identically from any machine or CI runner with no local state to go stale.
ftp / sftp
Ships the newest .bxa to a remote directory over plain FTP or SFTP, via the real bx:ftp component - a genuine runtime dependency of this project (like bx-ai), not vendored. remotePath is a remote directory - the uploaded file keeps its own name, the same convention ssh's scp target uses.
// deploy/ftp-production.bx
{
target : "ftp",
host : "ftp.example.com",
username : "deploy",
passwordEnvVar : "FTP_PASSWORD",
remotePath : "/uploads/my-agent",
port : 21, // optional, defaults to 21
passive : true, // optional, defaults to true
timeout : 30, // optional, seconds, defaults to 30
proxyServer : "proxy.company.com:8080" // optional
}
// deploy/sftp-production.bx
{
target : "sftp",
host : "sftp.example.com",
username : "deploy",
key : "/home/me/.ssh/id_rsa", // passwordEnvVar OR key required
passphraseEnvVar : "SFTP_KEY_PASSPHRASE", // optional, only if the key itself is passphrase-protected
fingerprint : "SHA256:...", // optional host key verification
remotePath : "/uploads/my-agent",
port : 22, // optional, defaults to 22
timeout : 30 // optional, seconds, defaults to 30
}
Requires a prior bxAgents package. ftp requires a passwordEnvVar; sftp accepts either a passwordEnvVar or a key (SSH private key file path). passwordEnvVar/passphraseEnvVar name environment variables holding the real secret - never the secret value itself - resolved live at deploy time; key stays a plain path, since it's already not secret material itself. Every bx:ftp action throws on failure (connection refused, auth rejected, a negative server reply) rather than returning a soft failure - this target catches that and re-throws it as a clear BxAgents.DeployFailed, always closing the connection afterward even on error.
Secrets stay external
No target ever reads a secret (API token, SSH key, registry password) from deploy/* config - every credential is resolved from an environment variable at deploy time, matching this project's existing rule that provider API keys are never embedded in a build or package (see Deployment & Secrets):
| Target | Env var(s) |
|---|---|
ssh | none required - identityFile is a path to a key file you manage yourself |
docker | DOCKER_USERNAME, DOCKER_PASSWORD (both optional - only used if set) |
digitalocean | DOCKER_USERNAME/DOCKER_PASSWORD (for the image push) + DIGITALOCEAN_TOKEN (required) |
ftp / sftp | whichever env var(s) passwordEnvVar/passphraseEnvVar name - the entry itself only ever holds the env var's NAME, never its value (key is a path, same as ssh's identityFile) |
Validation
targetmust be one oflocal,ssh,docker,digitalocean,ftp,sftp.- Entry names must be unique across
deploy/*.bxanddeploy/*.json. - Each target's required fields (above) are checked when
deployruns -localneedsdestination,ssh/ftp/sftpneedhost/username/remotePath(the same field name across all three),docker/digitaloceanneedregistry.repository,digitaloceanalso needsappName,ftpalso needspasswordEnvVar,sftpalso needspasswordEnvVarorkey.