Dockerfiles used to deploy Astro and PocketBase on Railway
By Flavio Copes
The two Dockerfiles I used to deploy Astro and PocketBase on Railway, and why avoiding Alpine Linux fixed internal networking between the services.
Here’s a super quick way to deploy Astro and PocketBase on Railway (referral link).
The setup is two separate Railway services. One runs PocketBase, the backend that gives me a database, auth and an API in a single executable. The other runs the Astro site in SSR mode, which talks to PocketBase.
Each service has its own Dockerfile. When Railway finds a Dockerfile in the repo, it uses it to build the deploy instead of its automatic builder, so you get full control over the image.
The PocketBase Dockerfile
I used this Dockerfile to deploy PocketBase:
FROM ubuntu:latest
ARG PB_VERSION=0.20.4
# Install unzip and ca-certificates
RUN apt-get update && \
apt-get install -y \
unzip \
ca-certificates && \
rm -rf /var/lib/apt/lists/*
# download and unzip PocketBase
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip
RUN unzip /tmp/pb.zip -d /pb/ && \
rm /tmp/pb.zip
EXPOSE 8080
# start PocketBase
CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8080"]
PocketBase ships as a single binary, so the image stays basic. The ADD instruction downloads the release zip straight from GitHub, and unzip extracts the binary to /pb/.
The PB_VERSION build argument pins the version. When a new PocketBase release comes out, I bump that one line and redeploy.
Note the --http=0.0.0.0:8080 flag. Inside a container, the server must listen on all interfaces, not just localhost, or nothing outside the container can reach it.
The Astro Dockerfile
and this Dockerfile to deploy SSR Astro:
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
Same idea here with HOST=0.0.0.0, this time as an environment variable the Astro Node adapter reads. The build runs inside the image, and the final command starts the server entry that npm run build produced.
The dependencies get installed fresh inside the container. That avoids dragging in a node_modules folder built on my Mac, which may contain binaries compiled for the wrong platform.
The Alpine Linux gotcha
My first attempt used Alpine-based images, because they’re smaller. That was a mistake in this case.
Railway services can talk to each other over private networking, using *.railway.internal hostnames. With Alpine as the base image, my Astro service could not resolve the PocketBase internal hostname, so every request between the two failed.
Avoiding Alpine Linux as the base image fixed an issue with internal links I had, so now the 2 instances can talk via *.railway.internal internal Railway URLs.
One more thing to set up: PocketBase writes its SQLite database to a pb_data folder. Container filesystems are wiped on every deploy, so attach a Railway volume to the PocketBase service, or you’ll lose your data on the next redeploy.
If you need a Dockerfile for a different setup, I made a free Dockerfile generator that covers Node.js, Next.js, Astro, Bun and static sites.
Related posts about docker: