I’ve been trying to get a Node/Express/Prisma app deployed to Fly.io.
I can deploy the app, see the Express server start, and health checks pass upon deployment.
However the server does not respond to visiting the Fly.io URL for the app, eventually resulting in a 503 error.
The server responds when run locally. I have checked the internal port configuration, worked through the troubleshooting steps and I’m out of ideas!
Things that might be unusual:
- I’m running the server using
tsx
- I’ve specified
18.16.1-bullseye-slim
as my base image (to match my local node version. Bullseye was required to get python dependency) - I had to change the import statement in docker-entrypoint.js to a CommonJS import because I was getting errors that I could not import ES6 packages outside a module
- I am developing on Windows
flyctl services list
Services
PROTOCOL PORTS HANDLERS FORCE HTTPS PROCESS GROUP REGIONS MACHINES
TCP 80 => 3000 [HTTP] True app sin 1
TCP 443 => 3000 [HTTP,TLS] False app sin 1
fly.toml
app = "app-api"
primary_region = "sin"
[build]
dockerfile = "./docker/Dockerfile"
ignorefile = "./docker/.dockerignore"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
[[http_service.checks]]
grace_period = "10s"
interval = "30s"
method = "GET"
timeout = "5s"
path = "/signin/google/success"
Dockerfile
# syntax = docker/dockerfile:1
# Adjust NODE_VERSION as desired
ARG NODE_VERSION=18.16.1
FROM node:${NODE_VERSION}-bullseye-slim as base
LABEL fly_launch_runtime="Node.js/Prisma"
# Node.js/Prisma app lives here
WORKDIR /app
# Set production environment
ENV NODE_ENV=production
# Throw-away build stage to reduce size of final image
FROM base as build
# Install packages needed to build node modules
RUN apt-get update -qq && \
apt-get install -y python pkg-config build-essential openssl
# Install node modules
COPY --link package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production=false
# Generate Prisma Client
COPY --link prisma .
RUN npx prisma generate
# Copy application code
COPY --link . .
# Remove development dependencies
RUN yarn install --production=true
# Final stage for app image
FROM base
# Install packages needed for deployment
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y openssl && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Copy built application
COPY --from=build /app /app
# Entrypoint prepares the database.
ENTRYPOINT [ "/app/docker/docker-entrypoint.js" ]
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD [ "yarn", "run", "start" ]
docker-entrypoint.js
#!/usr/bin/env node
const spawn = require("node:child_process").spawn;
const env = { ...process.env };
(async () => {
// If running the web server then migrate existing database
if (process.argv.slice(2).join(" ") === "yarn run start") {
await exec("npx prisma migrate deploy");
}
// launch application
await exec(process.argv.slice(2).join(" "));
})();
function exec(command) {
const child = spawn(command, { shell: true, stdio: "inherit", env });
return new Promise((resolve, reject) => {
child.on("exit", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`${command} failed rc=${code}`));
}
});
});
}
server.js
"use strict";
...
import express from "express";
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
...
...
app.get("/signin/google/success", (req, res) => {
res.send("success");
});
...
// Start server
app.listen(port, "::", () =>
console.log(`🚀 Server ready, listening on port ${port}`)
);
Start command
npx tsx server.js