Skip to content

The Shipping Manifest

This is the compact field reference for the deployment phase of Voyager’s Log.

Not a full lesson.
Not a walkthrough.
A working reference sheet.

Use it when you need to remember:

  • what changed between local and hosted
  • what Render needs
  • what Atlas is doing
  • what the Dockerfile is responsible for
  • how to classify deployment failures without spiraling

PhaseWhat happensMain question
1. Atlas SetupThe database moves from local MongoDB to MongoDB AtlasCan the app reach the hosted database?
2. Render Service SetupA Web Service is created from the GitHub repoCan the platform build and run the app?
3. Environment SetupRuntime variables are added in RenderDoes the deployed app have the config it needs?
4. Image BuildRender builds the app from the DockerfileDid the image build successfully?
5. Runtime StartupThe container starts and the app attempts to bootDid the app become healthy?
6. External RoutingThe public URL serves the frontendDoes the hosted app load in a browser?
7. Persistence CheckLive data moves through the app into AtlasDoes the deployed stack actually work end to end?

Local DevelopmentHosted Deployment
MongoDB container in ComposeMongoDB Atlas
Express backend in DockerExpress in Render
Vite dev server on hostBuilt frontend served by Express
Compose networking by service nameExternal connectivity through environment variables
Local .env or defaultsRender environment variables
Files on your machineFiles from the committed repo
Same app, different environment

Deployment did not create a totally different application.

It changed how the application is built, configured, connected, and hosted.


These are the key variables for the deployed Voyager’s Log app.

VariablePurposeExample / Notes
MONGO_URIConnects Mongoose and session storage to AtlasUse the Atlas connection string that already worked locally
SESSION_SECRETSigns session data securelyMust be long, random, and not committed
NODE_ENVEnables production-specific behaviorSet to production
PORTPlatform-provided runtime portDo not set manually in Render

Our production Dockerfile does three important jobs:

ResponsibilityWhat it means
Build the frontendRun the Vite production build
Package the backendInstall only the backend runtime dependencies
Assemble the final imageCopy the built frontend into the runtime container
# Stage 1: build frontend
RUN npm run build --prefix client
# Stage 2: run app
COPY --from=builder /app/client/dist ./client/dist
CMD ["node", "server/index.js"]

That means the final container contains:

  • the Express server
  • the backend dependencies
  • the compiled frontend assets

It does not contain a live Vite dev server.


In deployment, Express does more than just serve API routes.

It also needs to serve the compiled frontend.

  • serve static files from client/dist
  • return index.html for the app shell
  • expose the API routes
  • listen on process.env.PORT
  • bind to 0.0.0.0
const PORT = process.env.PORT || 3000;
const HOST = process.env.HOST || '0.0.0.0';
if (process.env.NODE_ENV === 'production') {
const distPath = path.join(__dirname, '../client/dist');
app.use(express.static(distPath));
app.get('/{*splat}', (req, res) => {
res.sendFile(path.join(distPath, 'index.html'));
});
}
app.listen(PORT, HOST, () => {
console.log(`Voyager's Log API listening on port ${PORT}`);
});
Classic gremlin

If NODE_ENV=production is missing, the app may boot successfully but fail to serve the frontend correctly.


SettingValue / Guidance
Service TypeWeb Service
RuntimeDocker
Repo AccessConnect GitHub account
Repo VisibilityPrivate is fine
Root DirectoryLeave blank if app is at repo root
Dockerfile Path./Dockerfile if Dockerfile is at repo root
BranchYour deployment branch, usually main
PORTLet Render provide it automatically

Atlas is now the production database.

That means it is responsible for:

  • storing voyage entries
  • storing user data
  • handling the deployed app’s reads and writes
  • replacing the old local MongoDB container

When persistence is working, you should be able to:

  1. submit a voyage entry in the live app
  2. log in as admin
  3. approve the entry
  4. see it appear in the public feed
  5. confirm the document in Atlas

That is the end-to-end proof.


Use this when a deploy is supposed to be working.

  • repo pushed to GitHub
  • Dockerfile exists in expected path
  • frontend build succeeds
  • final image is assembled
  • MONGO_URI is valid
  • SESSION_SECRET exists
  • NODE_ENV=production
  • app binds to process.env.PORT
  • app binds to 0.0.0.0
  • public Render URL opens
  • frontend loads
  • no Cannot GET /
  • no missing static asset failures
  • form submission works
  • moderation workflow works
  • Atlas documents are created or updated correctly

When something breaks, do not say only:

“deployment failed”

Classify it.

Failure TypeMeaningCommon Causes
Build FailureThe image never finished buildingbad Dockerfile path, failed npm install, failed Vite build, missing committed files
Runtime FailureThe image built, but the app failed to start or stay healthybad env vars, Atlas connection failure, startup crash, bad port binding
Routing FailureThe app is running, but the browser is not getting the frontend correctlymissing static-serving logic, wrong dist path, missing production mode
Persistence FailureThe frontend loads, but the data flow failsbroken API handler, bad Mongoose logic, Atlas read/write issues, session/auth problems

When a deploy fails, ask these in order:

  • No → build failure
  • Yes → go to question 2

2) Did the app start and behave like a healthy service?

Section titled “2) Did the app start and behave like a healthy service?”
  • No → runtime failure
  • Yes, but browser is wrong → routing issue
  • Yes, but data flow is broken → persistence issue

That simple.


Log TypeUse it for
Build LogsDocker steps, package installs, Vite build, image assembly
Runtime Logsstartup messages, port binding, Atlas connection, exceptions, request failures
  • Build logs tell you whether the platform packaged the app
  • Runtime logs tell you whether the deployed app is actually functioning
Remote terminal mindset

Render logs are basically the deployed version of your terminal output.

Once the app is hosted, that is the terminal that matters most.


SymptomLikely Layer
Deploy fails during npm run buildBuild
Render service never becomes healthyRuntime
Public URL shows Cannot GET /Routing
Homepage loads but submission failsPersistence
Login fails unexpectedlyPersistence / runtime config
Atlas shows no new document after form submissionPersistence
Build succeeds, but app crashes on startupRuntime

Local AssumptionProduction Reality
“Port 3000 is the app port”The platform provides the runtime port
“The DB is at dbThe DB is reached through MONGO_URI
“Vite serves the frontend”Express serves built frontend assets
“My local files are the source of truth”The committed repo is the source of truth
“If it works locally, deployment should work”Hosted runtime has different config, networking, and behavior

Voyager’s Log now works like this:

  1. GitHub provides the source
  2. Render builds the Docker image
  3. Render starts the container
  4. Express boots in hosted runtime
  5. Express serves the API and built frontend
  6. Mongoose connects to Atlas
  7. Users interact with the live deployed app

That is the operational picture.


If you remember nothing else, remember this:

  • Build asks: can the platform package the app?
  • Runtime asks: can the platform run the app?
  • Routing asks: does the browser get the frontend correctly?
  • Persistence asks: does the live app actually move data through the stack?

That is the whole deployment diagnostic map.


Beyond Localhost - Study Guide

Complete Remotely Deployed Voyagers Log Demo Repo


Over the Horizon

We’ve shipped the stack. Next, we move past deployment itself and start looking at what happens once an application is live: visibility, operational signals, and how to read the system once it leaves the harbor.