Stop redeploying. Each redeploy destroys the evidence and, worse, leaves partial state behind that changes the next attempt.
The status the UI shows you is the orchestrator's opinion of the container, not the application's. "Failed" means a process exited non-zero or a health check never passed. The reason is in the container's own log, and you have to go and get it.
Deploy it once more, and while it is in the deploying state — not after it gives up — get a shell on the host and list the running containers for that app, then follow the logs of each one. If the app ships a database alongside itself there will be more than one container, and the one that fails is usually not the one you were watching.
With the actual log in front of you, it is almost always one of three things:
1. Permissions on the host dataset. The app runs as a specific unprivileged user inside the container. If the dataset you pointed it at is owned by root, or carries an ACL that denies that user, the app cannot create its data directory and dies at startup. The log line is some form of permission denied on a path, and it is unmissable once you are reading the right log.
The fix is to set ownership of that dataset to the user and group the app expects, recursively, before deploying. Note that on a filesystem with ACLs, a permissive-looking mode can still be overridden by an ACL entry — check the actual ACL, not just the mode.
2. A non-empty data directory from a previous failed attempt. This is the one that makes it reproducible across four attempts. Bundled database engines refuse to initialise into a directory that already contains files, so attempt one leaves half a database behind and attempts two through four fail for a different reason than attempt one did — and look identical from the UI.
The fix is to genuinely empty the data path between attempts. If you created a fresh dataset each time you may have dodged this; if you reused one, this is very likely your problem.
3. A port already bound. The app wants a port something else is already holding. The log says address already in use. Cheap to rule out: list listening sockets on the host before deploying.
My money is on the second one, because it fits "identical result every time" better than the others do.