Dockerfile to run Astro Node SSR on fly.io

By

The Dockerfile I use to run an Astro Node SSR app on fly.io, plus the .dockerignore and fly.toml you need to make fly deploy work without errors.

~~~

This is the exact Dockerfile I use to deploy an Astro site with Node SSR to fly.io, along with the .dockerignore and fly.toml that make fly deploy work.

NOTE: this setup does not involve CI or auto deploy on commit.

You deploy from your local Astro project directly.

I’ll probably make another post for that.

Also read run an app on fly.io.

Also see my Docker tutorials.

I also built a free Dockerfile generator that creates Dockerfiles for Astro, Node.js, Next.js, Bun and static sites.

One prerequisite: your Astro project must use the Node adapter in standalone mode. That’s what generates the dist/server/entry.mjs file the container runs at the end.

Dockerfile:

FROM node:lts-slim as runtime
WORKDIR /app

# Ensure that both node_modules and package-lock.json are removed.
COPY package.json .
RUN rm -rf node_modules package-lock.json

# Perform a fresh installation of npm dependencies.
RUN npm install

# Copy the rest of your application files.
COPY . .

# Build your application.
RUN npm run build

# Set environment variables and expose the appropriate port.
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000

# Define the command to run your application.
CMD node ./dist/server/entry.mjs

A few things worth explaining. The fresh npm install matters because dependencies installed on your Mac can contain native code compiled for macOS, which won’t run in the Linux container. Installing inside the container gets you Linux builds.

HOST=0.0.0.0 makes the server listen on all interfaces, so fly.io can reach it from outside the container. The default localhost would only accept connections from inside.

NOTE: I found this Dockerfile in the official Astro docs:

FROM node:lts AS runtime
WORKDIR /app

COPY . .

RUN npm install
RUN npm run build

ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs

This wasn’t working for me on fly.io, but the one listed before (thanks ChatGPT) worked.

Also very important, add this .dockerignore (or the build will fail):

.DS_Store
node_modules
dist
.env
.git
.gitignore

Without it, COPY . . copies your local node_modules and dist into the image, overwriting the fresh Linux install the Dockerfile just did. That’s the failure I hit.

After running fly launch, from your local project folder, you should have this fly.toml file:

# fly.toml app configuration file generated for aha-test-flavio on 2024-01-03T13:48:33+01:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = "<your app name>"
primary_region = "<region>"

[build]

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0
  processes = ["app"]

[[vm]]
  cpu_kind = "shared"
  cpus = 1
  memory_mb = 1024

Notice that internal_port = 3000 matches the PORT set in the Dockerfile. If those two disagree, the deploy succeeds but the app never responds, and fly’s health checks fail with a connection refused error. Keep them in sync.

For environment variables, import existing .env ones using:

cat .env | fly secrets import

(or add them through Fly’s dashboard.)

Now run fly deploy --ha=false, from your local project folder, to deploy.

The --ha=false flag deploys a single machine instead of the two fly creates by default for high availability. For a side project, one machine is enough, and it keeps costs down.

Tagged: Astro, Docker · All topics
~~~

Related posts about astro: