The ssh-deploy goal is new in vermilingua 1.1.12 and marked experimental: it works for WebObjects and ng-objects applications, but details may change in coming releases. exe.dev is a third-party service with its own pricing and terms; nothing here depends on it beyond its VMs and its HTTPS proxy.
exe.dev rents Linux VMs that you manage over SSH, and puts each one behind HTTPS at https://<vm>.exe.xyz/ with the certificate handled for you. That is everything a WebObjects application needs, and vermilingua:ssh-deploy does the rest from your project folder:
mvn vermilingua:ssh-deploy -Ddeploy.exe.vm=myapp
The goal packages the application, creates the VM if it doesn't exist, uploads the .woa, installs a Java runtime, starts the application as a systemd service and points the VM's HTTPS address at it. The first run takes about twenty seconds, VM included. Every run after that uploads only what changed.
1. What has to be true first
- The project builds with vermilingua 1.1.12 or later and has
woapplicationpackaging. In a multi-module build the goal skips every module that isn't an application; see multi-module builds. - You have an exe.dev account with your SSH public key added to it.
- You have run
ssh exe.devonce in a terminal on the machine you deploy from. That records exe.dev's host key and shows that the account accepts your key. The goal connects without prompting, so it can't do this part for you. sshandrsyncare installed, which they are on any Mac or Linux machine. The goal runs them rather than a Java SSH library, so your keys, your agent and~/.ssh/configall apply.
2. The first deployment
Name the VM. Names are your choice, as long as they're letters, digits, ., _ and -, and they become the address:
mvn vermilingua:ssh-deploy -Ddeploy.exe.vm=myapp
After the build itself, the output is the deployment, step by step:
[INFO] Creating exe.dev VM myapp
Creating myapp using image boldsoftware/exeuntu...
[INFO] Waiting for myapp.exe.xyz to accept SSH connections
[INFO] Deploying MyApp to myapp.exe.xyz as release 20261019-151305
[INFO] Creating release folder
[INFO] Uploading MyApp.woa
Number of files transferred: 166
...
[INFO] Activating release 20261019-151305
Installing Java 25 (x64) to /home/exedev/vermilingua/java/25
Waiting for MyApp to answer on port 8080
MyApp is up
[INFO] Pointing https://myapp.exe.xyz/ at port 8080
https://myapp.exe.xyz/ now points to port 8080 (private).
[INFO] Deployed MyApp to https://myapp.exe.xyz/
Open the address and log in with your exe.dev account. The VM is private until you say otherwise: only people with access to it get past exe.dev's login page. To publish it, add -Ddeploy.exe.public=true, or run ssh exe.dev share set-public myapp yourself. share set-private turns it back.
To make the deployment one bare command, put the VM name in build.properties:
deploy.exe.vm = myapp
mvn vermilingua:ssh-deploy
3. Configuration
Every setting is read the same way as the launch.* settings: from build.properties, overridden by an environment file, overridden by -D properties on the command line. All of them are optional apart from where to deploy.
| Setting | Meaning |
|---|---|
| deploy.exe.vm | The exe.dev VM to deploy to. Created on the first deployment if it doesn't exist. |
| deploy.exe.public | true makes the VM's HTTPS address public. Leave it out and the address stays as it is, which is private for a new VM. |
| deploy.host | Deploy to any other Linux machine instead, given as an SSH destination: host or user@host. See beyond exe.dev. |
| deploy.appName | Names the systemd service (vermilingua-<appName>) and the application's folder on the machine. Defaults to the bundle name. Two applications on one VM need two names. |
| deploy.port | The port the application listens on, passed to it as -WOPort. Defaults to 8080. |
| deploy.args | More arguments for the application, after -WOPort. Anything the application would take on its command line, properties included. |
| deploy.java | The Java version to run on, as a feature version such as 25. Defaults to the version Maven itself runs on, which is the version that built the application. |
Per-environment values go in build.properties.<env>, selected with -Dbuild.env=<env>. A project with a staging VM and a production VM keeps a different deploy.exe.vm in each file:
mvn vermilingua:ssh-deploy -Dbuild.env=staging
4. What happens on the VM
Everything the goal puts on the machine lives in the SSH user's home folder, under vermilingua:
vermilingua/
java/25/ the Java runtime
apps/MyApp/
releases/20261019-151305/MyApp.woa one folder per deployment
releases/20261019-160212/MyApp.woa
current -> releases/20261019-160212 the release that runs
Each deployment works in a fixed order:
- Upload into a new release folder. rsync copies the
.woaand compares every file with the running release. Files that haven't changed aren't sent, and files that have changed are sent as differences. Rebuilding a 4 MB application after a code change sends tens of kilobytes. The running application isn't touched by any of this. - Install Java, if missing. An Eclipse Temurin runtime of the version in
deploy.java, downloaded once and kept undervermilingua/java. It doesn't depend on what the distribution's packages offer, and it doesn't touch any Java already installed on the machine. - Switch.
currentis pointed at the new release. - Restart. The service unit is written to
/etc/systemd/system/vermilingua-MyApp.serviceand the service is restarted. It runs the bundle's own launch script, with the installed Java and the port, as the SSH user, and systemd restarts it if it crashes and starts it when the VM boots. - Prune. Releases beyond the newest five are deleted.
- Wait. The goal waits up to a minute and a half for the application to answer on its port. If the service fails instead, the goal prints the end of its log and fails the build, so a deployment that doesn't start can't look like a success.
On exe.dev the goal then points the VM's HTTPS address at the application's port, which is the share port line in the output.
5. HTTPS and your own domain
exe.dev terminates TLS and forwards plain HTTP to the application, with X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-For headers saying what the browser asked for. The application keeps listening on plain HTTP and there is no certificate to look after.
URLs the application generates are relative, so they work as they are. Code that builds absolute URLs needs to know the request arrived over HTTPS, and should read X-Forwarded-Proto to find out. wonder-slim's ERXRequest already does: a request with X-Forwarded-Proto: https counts as secure.
For your own domain, add a CNAME record pointing at myapp.exe.xyz, then link the domain to the VM:
ssh exe.dev domain add myapp app.example.com
exe.dev issues the certificate for it automatically. A CNAME can't sit on an apex domain such as example.com itself, so an apex needs a DNS provider that offers ALIAS records; check exe.dev's documentation for the details.
exe.dev makes one port per VM public. That is all a single application needs. To run several applications or instances on one VM behind one address, put modulo on that port to route between them.
6. Redeploying and rolling back
Redeploying is the same command. A new release folder is uploaded next to the running one, and only the restart interrupts the application, for the seconds it takes to start. Sessions are in memory, so they don't survive the restart: users are logged out and their page state is gone. Deploy at a quiet moment if that matters.
Rolling back is pointing current at an earlier release and restarting the service, because the last five are still on the machine:
ssh myapp.exe.xyz
cd vermilingua/apps/MyApp
ls releases
ln -sfn releases/20261019-151305 current
sudo systemctl restart vermilingua-MyApp
7. Looking at the running application
The VM is an ordinary Linux machine, so the ordinary tools apply. The application's output goes to the systemd journal:
ssh myapp.exe.xyz
systemctl status vermilingua-MyApp
journalctl -u vermilingua-MyApp -f
To stop the application without deleting anything, use sudo systemctl stop vermilingua-MyApp, and disable to keep it from starting at boot. ssh exe.dev rm myapp deletes the VM and everything on it.
8. Multi-module builds
Run on its own, the goal packages the application before deploying it. In a build with several modules, that package step can't see the jars of the modules the application depends on, and the build fails resolving them. Name the package phase as well, and select the application:
mvn package vermilingua:ssh-deploy -pl MyApp -am
The same applies to vermilingua:run.
9. Beyond exe.dev
Only creating the VM and pointing its HTTPS address are specific to exe.dev. Everything else works on any Linux machine with systemd that you can reach over SSH:
mvn vermilingua:ssh-deploy -Ddeploy.host=deploy@server.example.com
The SSH user must be able to run sudo without a password, because writing and restarting the service needs it. exe.dev's VMs are set up that way; on your own servers, give a dedicated deploy user exactly that. The machine also needs curl and rsync. HTTPS is then up to you, for example with modulo on ports 80 and 443 in front of the application.
10. When it doesn't work
- "Couldn't reach exe.dev over SSH": run
ssh exe.devin a terminal. A host key prompt means you haven't connected from this machine before; accept it. A login or signup prompt means exe.dev doesn't know your key yet; add it to your account. - The address redirects to a login page: the VM is private, which is the default. Log in, or make it public with
-Ddeploy.exe.public=true. - "didn't start answering on port 8080", followed by a log: the application failed to start, and the log is the reason. The previous release is still on the machine; see rolling back.
- The address shows an error instead of the application, while the deployment said the application is up: the address isn't pointed at the application's port. That happens when the deployment stopped before its last step. Run it again, or point the address yourself with
ssh exe.dev share port myapp 8080. - "Could not resolve dependencies" in a multi-module build: see multi-module builds.
- sudo asks for a password, or says a terminal is required, on your own server: the SSH user needs passwordless
sudo; see beyond exe.dev. - "Uploading … failed. Is 'rsync' installed and on the PATH?": install rsync on the machine you deploy from.
Everything above matches vermilingua 1.1.12. The release notes say what changed since.