If OpenShip reports a completed deployment but your domain cannot be reached, isolate the failure across five layers: local build, SSH transfer, container startup, domain routing, and dependent services. Do not reinstall the platform until the evidence shows a platform-level fault.
This guide is for first-time OpenShip users blocked during a build or launch, engineers maintaining their own servers, and remote teams deploying from a Mac that cannot stay online or provide enough build capacity.
OpenShip deployment failure is usually a broken handoff between stages, not one mysterious error. OpenShip builds the image on your machine or in the cloud, transfers it over SSH, starts a container on the target, and routes the domain through its edge layer. Each handoff produces different evidence, so collect that evidence before changing configuration. (OpenShip documentation)
Start with the five-layer fault map
Use this sequence as your incident timeline:
- Build: Did the project produce a valid image or deployment artifact?
- Connection: Could the control machine authenticate to the target server and transfer the artifact?
- Runtime: Did the container start, listen on the expected address, and pass its health check?
- Routing: Does public DNS point to the correct edge or server, and does HTTPS complete?
- Dependencies: Can the application reach its database, cache, queue, storage, or other backend service?
The fastest way to waste time is to change all five layers at once. If you edit the Dockerfile, rotate SSH keys, change DNS, and recreate the database together, you lose the ability to identify the original cause.
Keep the previous working version available until the replacement passes a real request, a health check, and a data operation.
A useful evidence bundle contains:
- The complete build log, not only the final error line.
- The process exit code.
- The relevant
Dockerfile, package manifest, lock file, start command, and deployment configuration. - The exact target address and SSH port.
- Container status, startup logs, restart count, and health status.
- Public DNS answers from more than one resolver.
- Certificate status and the HTTP response from the public domain.
- Database connectivity results without exposing credentials.
Milestone 1: Prove whether the build actually failed
A deployment interface can show a failed build as a short summary such as “command exited with code 1,” “artifact missing,” or “build completed without output.” That summary is not enough to identify the cause.
Which part of the OpenShip build log should you inspect first?
Start at the first meaningful failure in the complete log, then compare it with the final exit code. The first meaningful failure may be a package download error, an unsupported runtime, a failed compilation command, a missing environment variable, or an application test failure. The last line often only reports that a previous command returned a non-zero status.
Check these four areas in order:
Dependency installation
Look for failed package resolution, authentication to a private package registry, lock-file conflicts, native module compilation errors, or a package manager command that never completed.
Your evidence should include:
<full-build-log>
<package-manager-output>
<exit-code>
<package-manifest>
<lock-file>
If the same dependency installation fails locally with the same lock file, treat it as a project configuration problem. If the local build succeeds but OpenShip fails with a different runtime or missing system library, compare the build environment rather than rewriting application code.
Build and compile commands
Confirm the command OpenShip is executing against the command defined by the project. A common failure pattern is a successful dependency step followed by a missing script, wrong working directory, monorepo path, or build command that produces output in a different directory than the runtime expects.
Inspect:
cat package.json
cat Dockerfile
git status --short
git rev-parse HEAD
For another language or framework, replace these files with the project’s own build manifest and container definition. Do not assume a generic command such as npm run build is correct for every repository.
Runtime version mismatch
A project may compile on your Mac and fail in the remote build environment because the runtime, package manager, system library, or CPU architecture differs. Record the runtime versions used locally and by the build process:
node --version
npm --version
python3 --version
uname -m
Use only the commands relevant to your project. The important evidence is the version pair, not the command itself.
Environment variables
Separate variables required during compilation from variables required only when the application starts. A public API endpoint may be needed during a frontend build, while a database password should normally remain a runtime secret.
Check for:
- A variable referenced in code but absent during build.
- A variable defined only in your local shell.
- A variable using the wrong environment name.
- A secret accidentally committed to the repository.
- A build-time value pointing to a development service.
How do you distinguish local resource exhaustion from a code configuration error?
Resource exhaustion leaves operating-system evidence: the build process is killed, memory pressure appears, disk space is exhausted, or the machine becomes unresponsive. Code configuration errors produce repeatable command failures with readable compiler, package manager, or test output.
On the build machine, capture:
df -h
free -h
uptime
On macOS, use the system memory and storage monitors rather than assuming that a successful interactive build means a long deployment build will also succeed. If the machine cannot remain available for the whole build-and-transfer window, move the build control plane to a continuously available remote environment instead of repeatedly retrying from a sleeping laptop.
The documented OpenShip deployment path places the build on the initiating machine or in a cloud environment, while the production server receives the built artifact. That boundary matters: a production server can be healthy while the machine that performs the build is the real bottleneck. (OpenShip deployment overview)
Milestone 2: Separate SSH authentication from transfer failure
OpenShip uses SSH to reach a target server and stream the built image. That creates three distinct failure classes:
- The first connection never authenticates.
- Authentication succeeds but the transfer stops.
- The command runs but lacks permission to perform the deployment action.
What should you check when OpenShip cannot connect through SSH?
Run a direct, non-interactive test with placeholders:
ssh -vvv \
-i /path/to/<PRIVATE_KEY> \
-p <SSH_PORT> \
<DEPLOY_USER>@<SERVER_ADDRESS> \
'echo connected && id && uname -a'
This test should produce four useful evidence points:
- The resolved target address and port.
- The host key or known-hosts decision.
- Successful public-key authentication.
- The remote user identity and operating system response.
Check the target address separately:
dig +short <SERVER_ADDRESS>
nc -vz <SERVER_ADDRESS> <SSH_PORT>
If the address resolves to the wrong IP, the problem is not the SSH key. If the port is unreachable, inspect the server firewall, provider firewall, routing, and SSH daemon configuration. Do not expose additional ports just to make the deployment work.
For the private key, verify that the file exists, is readable by your user, and is not broadly accessible:
ls -l /path/to/<PRIVATE_KEY>
chmod 600 /path/to/<PRIVATE_KEY>
For first-time connections, verify the host fingerprint through a trusted server console or provider panel. Do not disable host-key verification as a permanent workaround. The SSH client’s verbose output is more valuable than a generic “permission denied” message because it shows whether the failure occurs before authentication, during key negotiation, or after login.
What evidence proves that the problem is transfer interruption rather than authentication?
A successful echo connected test proves that the control machine can establish an authenticated session. If the deployment still fails, capture:
- The final successful SSH connection line.
- The transfer error and timestamp.
- Available disk space on the target.
- The server’s network or firewall events.
- Whether the remote container service is responsive.
Run:
ssh -i /path/to/<PRIVATE_KEY> -p <SSH_PORT> \
<DEPLOY_USER>@<SERVER_ADDRESS> \
'df -h; docker version; docker info'
If the session connects but docker returns a permission error, add the deployment user to the correct operating-system group only after reviewing the server’s security model. Do not solve a permissions problem by running the entire deployment as unrestricted root unless you have a documented reason and rollback plan.
Use the OpenShip installation documentation to confirm the supported connection model before changing server-side components. OpenShip’s documented flow does not require installing an agent or dashboard on every target box, so an unexpected daemon requirement is a signal to check the command or installation path you are using.
Milestone 3: Diagnose containers that start and immediately restart
A deployment can be marked complete while the application is unusable. The image may exist, the container may have been created, and the routing layer may be configured, but the process can still exit immediately or fail its health check.
How do you investigate an OpenShip application that keeps restarting?
First identify the container and capture its state:
docker ps -a
docker logs --tail=200 <CONTAINER_NAME>
docker inspect <CONTAINER_NAME>
docker inspect -f '{{.RestartCount}}' <CONTAINER_NAME>
The container log is the primary view of standard output and error streams. The inspection result also reveals restart count and runtime state. If the application writes only to internal log files, the container log may be incomplete, so inspect the application’s own logging configuration as well. (Container logging reference)
Then check four runtime boundaries:
Entry command
Confirm that the image’s entrypoint or command starts the production process rather than a development server, shell, migration command, or one-shot task.
Listening address
A process bound only to 127.0.0.1 inside the container may not be reachable through the container network. The application normally needs to listen on the interface expected by its deployment configuration.
Port agreement
Compare the application’s internal listening port with the port OpenShip expects to route. A port listed in a Dockerfile is not the same as a process that is actually listening. Use:
docker exec <CONTAINER_NAME> sh -c \
'ss -lntp || netstat -lntp'
If the image does not contain either tool, inspect the application startup log or add a temporary diagnostic layer. Do not leave debugging utilities in the production image without a reason.
Health check
A health check can fail even when the process is running. It may call the wrong path, use the wrong port, require a dependency that is not ready yet, or reject a valid response because of an incorrect status expectation.
Container health checks expose status, failure streak, and command output through inspection:
docker inspect \
--format='{{json .State.Health}}' \
<CONTAINER_NAME>
The container health-check reference explains the available health-check controls and the evidence stored in the health state.
Repair conclusion: If the process exits with a clear application error, fix the entry command, environment, migration, or dependency configuration. If the process stays alive but health checks fail, fix the probe path, address, port, or readiness condition. If the host kills the process under memory pressure, move the build or runtime workload to a more suitable node rather than masking the failure with unlimited restart attempts.
After repair, deploy a new immutable version while keeping the old version available. Use rollback instead of deleting the last known-good release during investigation.
Milestone 4: Verify DNS, HTTPS, and the public request path
When OpenShip says the deployment succeeded but the domain does not open, test from the outside in:
- Public DNS resolution.
- Certificate issuance and hostname coverage.
- Edge or reverse-proxy routing.
- Application response.
- Local-only access on the server.
Why can an OpenShip deployment succeed while the domain remains unreachable?
The deployment artifact and container can be healthy while the domain still points to an old address, the certificate request cannot validate, the edge routes to the wrong service, or the application returns an error after the request reaches the server.
Run these checks from your Mac or another external network:
dig <YOUR_DOMAIN> A
dig <YOUR_DOMAIN> AAAA
dig <YOUR_DOMAIN> @8.8.8.8
dig <YOUR_DOMAIN> @1.1.1.1
curl -I http://<YOUR_DOMAIN>
curl -vk https://<YOUR_DOMAIN>
Compare the returned address with the intended target. Comparing results across public resolvers helps identify stale records, delegation problems, and split-horizon DNS behavior. (Public DNS troubleshooting guide)
For HTTPS, record:
- The certificate subject and alternative names.
- The certificate validity state.
- The hostname requested by the client.
- The HTTP status returned after TLS negotiation.
If certificate issuance fails, verify that the domain resolves to the expected route and that the required validation path is publicly reachable. The certificate challenge documentation explains the difference between HTTP-based and DNS-based validation.
What should you inspect when the application works only from the server itself?
Run a local request and compare it with an external request:
curl -i http://127.0.0.1:<APP_PORT>
curl -i -H 'Host: <YOUR_DOMAIN>' http://127.0.0.1:<EDGE_PORT>
If the local application response is healthy but the public request fails, focus on DNS, firewall exposure, edge routing, TLS, or the server’s public interface. If both local and public requests fail, return to the container and dependency layers.
Change one variable at a time. Do not replace DNS records, certificates, and application ports in the same edit. That turns a diagnosable routing problem into a moving target.
Milestone 5: Recover database and backend service failures safely
Database errors often look like application bugs because the application is the component reporting them. Separate the failure into three categories:
- The database service is unavailable.
- The credentials or connection string are wrong.
- The application code sends an invalid query or expects a missing schema.
How should you approach an OpenShip database connection failure?
Start with service status and network visibility:
docker ps -a
docker logs --tail=200 <DATABASE_CONTAINER>
docker inspect <DATABASE_CONTAINER>
Then test the connection from the application’s network context, not only from the host. A database published to the host may still be unreachable through the internal service name, while a private service may be intentionally inaccessible from the public internet.
Confirm:
- The database container is running.
- The application and database share the intended private network.
- The hostname points to the service name for the correct environment.
- The port matches the database service.
- The username and password belong to that environment.
- The selected database and schema exist.
- The data volume is mounted and still contains expected data.
Never delete a data volume as a generic repair step. Before any recovery operation:
docker volume ls
docker inspect <VOLUME_NAME>
Create and verify a backup using the database’s supported backup tool. Record the backup location and test that the backup can be read before attempting a migration or restore.
A connection refused error usually points to service availability, network binding, or the wrong port. An authentication error points to credentials or the selected database. A successful connection followed by a missing-table or migration error points to schema state or application versioning. Those conclusions require different fixes.
If the deployment contains Postgres, Redis, or another managed service, follow the service’s documented readiness and backup behavior instead of treating it as an ordinary stateless container. OpenShip describes backend services as privately networked with the application, so a public connectivity test is not proof that the application’s internal service path is correct.
Use this decision table before changing the environment
| Evidence you have | Most likely layer | First action | When to change environment |
|---|---|---|---|
| Build exits during dependency or compile step | Build configuration or local resources | Compare full logs, versions, lock files, and resource state | Move the build only when resources are the limiting factor |
| SSH cannot resolve or reach target | Address, port, firewall, or routing | Test DNS, TCP reachability, and verbose SSH | Change server only if the target is unavailable or unsuitable |
| SSH succeeds but remote command lacks permission | User or container permissions | Capture id, service status, and permission output |
Rebuild the server image only when permissions are irreparably inconsistent |
| Container exits immediately | Entry command, environment, or dependency | Inspect logs, exit state, and restart count | Change node only after confirming host resource pressure |
| Container runs but health is unhealthy | Port, address, probe, or readiness | Inspect health output and test locally | Change deployment platform only if the runtime cannot express the requirement |
| Public DNS fails | DNS or delegation | Compare public resolver answers and authoritative records | Move DNS only when the current provider cannot support the required routing |
| Local request works but public request fails | Edge, firewall, TLS, or route | Compare local, host-header, HTTP, and HTTPS requests | Change environment only after the public path is proven broken |
| Database connection fails | Service, network, credentials, or schema | Check service logs, network, credentials, and backup state | Move the database only after backup and data portability are confirmed |
This table gives you a decision boundary rather than a list of generic commands. Continue repairing the same environment when the evidence points to code, configuration, or a known network rule. Consider a different build node or always-on remote environment when the local machine lacks memory, storage, network continuity, or uptime.
Complete the release acceptance timeline
Do not close the incident when the dashboard turns green. Record a short acceptance run with these milestones:
Build accepted
- The full build log is stored.
- The commit identifier is recorded.
- The image or artifact is tagged.
- The build exits successfully.
- The runtime version and architecture are known.
Service accepted
- The new container is running.
- The entry command is the intended production command.
- The application listens on the expected interface and port.
- The health check returns healthy.
- The restart count remains stable during observation.
Public route accepted
- Public DNS returns the intended address.
- HTTPS presents a certificate covering
<YOUR_DOMAIN>. - The public request returns the expected status.
- A basic application route works from outside the server.
Data path accepted
- The application connects to the intended database environment.
- A safe read succeeds.
- A controlled write and read-back succeed where appropriate.
- The backup location and restore procedure are documented.
- No production volume was removed during troubleshooting.
Rollback accepted
- The previous release remains available.
- You know the rollback command or dashboard action.
- A rollback test has been performed in a controlled window.
- The team knows which version is safe to restore.
Use this remote development environment troubleshooting guide as a companion when the failure is caused by an unstable local control machine rather than by OpenShip itself. For a team that needs a continuously reachable Mac build node, compare the US East Mac rental option with your current local setup before changing the deployment platform.
Decide whether to keep repairing or move the build node
Keep repairing the current environment when the evidence shows a wrong command, missing variable, invalid route, incorrect SSH permission, or recoverable database configuration.
Move the build process to a continuous remote environment when:
- The local Mac repeatedly runs out of memory or storage during builds.
- The deployment stops when the laptop sleeps, changes networks, or leaves the office.
- Multiple team members need the same build environment.
- You need logs and artifacts to remain available after the initiating device disconnects.
- The project requires a stable architecture and runtime that your current machine cannot reproduce.
That decision does not mean OpenShip is the failed component. OpenShip’s documented architecture places the build on the initiating machine or cloud environment, so the build control plane becomes part of your production delivery design.
Your current setup may be a local Mac, a short-lived laptop session, or a server that is also handling production traffic. Those options can fail through limited build resources, interrupted SSH sessions, missing logs, and unclear ownership of the runtime environment. If you have confirmed that local capacity or continuous availability is the real constraint, renting a Kvmkit Mac gives you a reachable build node that can retain logs, support remote access, and stay separate from your daily workstation.
Before the next deployment, save this evidence template in your team runbook:
Incident:
Project:
Commit:
Target:
OpenShip version:
Build command:
Build exit code:
Build log location:
SSH target and port:
SSH authentication result:
Transfer result:
Container name:
Container exit code:
Restart count:
Health status:
Local HTTP result:
Public DNS result:
HTTPS certificate result:
Public HTTP result:
Database service status:
Database read/write test:
Backup location:
Known-good release:
Rollback result:
Final repair conclusion:
If every field has evidence, “deployment complete” becomes a verifiable release state rather than a dashboard label.
Run CI/CD on M4 Mac mini — the hassle-free way
Xcode, Fastlane, CocoaPods, and SPM are first-class on macOS. Mac mini M4 unified memory keeps signing and archiving smooth; ~4W standby power suits 24/7 build nodes.