The Code Uploader is a library that uploads a generated application to a machine and runs it with Docker Compose, reporting progress step by step. It has strategies for a local Docker, any SSH server (Debian/Ubuntu, and RPM based distros) and AWS EC2 instances.
npm install @lbdudc/gp-code-uploader- Node.js 18+
- An OpenSSH client (
ssh,scp) on the machine that deploys (SSH/AWS strategies) - Local: Docker with the compose plugin (Docker Desktop or Docker Engine)
- SSH: a user with passwordless
sudo(only needed the first time, to install Docker) and key based authentication. Password prompts are never shown: the connection fails fast instead of hanging.
import { Uploader, DebianUploadStrategy } from "@lbdudc/gp-code-uploader";
const uploader = new Uploader();
uploader.setUploadStrategy(new DebianUploadStrategy());
const { url } = await uploader.deploy(
{
host: "203.0.113.5",
port: 22,
username: "ubuntu",
certRoute: "/home/me/.ssh/id_ed25519",
repoPath: "./output", // generated app: must contain deploy/docker-compose.yml
remoteRepoPath: "/home/ubuntu/app", // absolute path, wiped on every deploy
// projectName: "my-app", // optional: compose project name (default: COMPOSE_PROJECT_NAME
// // from deploy/.env, else the folder name "deploy")
},
{
onEvent: (event) => console.log(event),
// signal: abortController.signal, // cancel a running deployment
},
);
console.log(`Deployed at ${url}`);deploy() resolves once every service is ready: healthy when it has a healthcheck, running otherwise, or exited with code 0 for one-shot services (importers, init containers). If a service fails or does not get ready in 10 minutes the promise rejects with that service's last log lines.
| Strategy | Steps |
|---|---|
LocalUploadStrategy |
Check Docker, stop previous deployment, build & start services, wait for services |
PackageStrategy |
Create the zip (gispublisher's --generate --zip): zips repoPath under a folder of the zip (plus extraFiles, e.g. a README and start scripts) into file. Nothing is deployed; the result has file instead of a URL |
DebianUploadStrategy |
Check the domain (only with domain), package code, connect to server, prepare server (installs Docker if missing), stop previous deployment, upload code, build & start services, wait for services |
HetznerStrategy, DigitalOceanStrategy |
Create the server (finds it by serverName, else creates it with your public ssh key and a firewall for 22/80/443; skipped when host is given), then the same steps as SSH, as root. Config: cloudToken, serverName, certRoute (the private key; <certRoute>.pub is uploaded), optional serverSize, serverRegion, serverImage, and publicKey to give the key as text. Plain REST calls with fetch (injectable as fetchFn for tests). Not tried against the real services, only against a fake API and to see the real APIs refuse a wrong token |
AWSUploadStrategy |
Check the firewall (only with domain: the security group must open 80 and 443), Create AWS instance (skipped when host is given), then the same steps as SSH. Retries the first connection while the instance boots |
| Key | Used by | Description |
|---|---|---|
repoPath |
all | Folder of the generated app (contains deploy/docker-compose.yml) |
projectName |
all | Optional compose project name (-p). Without it compose uses COMPOSE_PROJECT_NAME from deploy/.env, else the folder name. Set it when several apps share the same folder name |
resetData |
all | true deletes the previous deployment's volumes (down -v, i.e. the database) before starting. Default false: a redeploy keeps its data |
url |
all | URL returned as the result (default http://localhost / http://<host>) |
host, port, username, certRoute |
ssh, aws | SSH target and key |
domain |
ssh, aws | The app is served over HTTPS at this name (by the stack's own HTTPS front). Adds a Check the domain step: the name must lead to the server, or the deploy stops before connecting (only a warning on a server the deployment has just created). The result URL is https://<domain> |
file, name, extraFiles |
PackageStrategy |
Where to write the zip, the folder inside it, and files that are not on disk (relative path to content) |
remoteRepoPath |
ssh, aws | Absolute remote folder (validated: no .., spaces or quotes) |
AWS_* |
aws | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_AMI_ID, AWS_INSTANCE_TYPE, AWS_INSTANCE_NAME, AWS_SECURITY_GROUP_ID, AWS_KEY_NAME. AWS_USERNAME, AWS_SSH_PRIVATE_KEY_PATH and REMOTE_REPO_PATH are accepted as aliases of username, certRoute and remoteRepoPath |
onEvent receives objects like:
{ type: "step", id: "upload", label: "Upload code", status: "running", index: 5, total: 7 }
{ type: "step", id: "upload", label: "Upload code", status: "done", index: 5, total: 7, durationMs: 8123 }
{ type: "log", step: "build", line: "#12 [server 4/6] RUN ./gradlew build" }
{ type: "services", services: [{ name: "server", state: "running", health: "starting", status: "pending" }] }status of a step is running, done, skipped or failed. A failed step also sets error.step on the rejected error. Without onEvent, progress is printed to the console.
Every failure rejects deploy(). Errors from commands are CommandErrors (command, code, stderr, tail(n)). Aborting the signal kills the running command.
uploadCode(config)still works (console output, resolves to the URL); preferdeploy(config, { onEvent }).forceBuildis removed: the client and server are built inside Docker, so nothing is built on the machine that deploys.Uploader.executeCommandand the strategy hooksconfigureInstance/runDockerComposeUpare removed.docker-composev1 is only used as a fallback;docker composeis preferred.- Commands run without a shell and with
BatchMode=yes: a key that needs a passphrase must be loaded in an ssh-agent. - The remote folder is emptied on every deploy, and must be an absolute path at least two levels deep.
- An SSH key pair guide
- A security group with inbound SSH (22) and HTTP (80) guide
- IAM permissions to run instances (e.g.
AmazonEC2FullAccess) and your access/secret key guide - An instance with enough memory: the build fails on the smallest free tier instances
npm test # unit tests, no Docker/SSH needed
npm run lintVictor Lamas Email: victor.lamas@udc.es
This project is licensed under the MIT License - see the LICENSE file for details