Unable to deploy turborepo + astro + payload + pnpm monorepo

Thanks in patrikaww I've been able to get as far as building the application, but I'm not able to deploy it. stack is: - turborepo - pnpm workspaces - payloadcms - astro - mongodb atlas When using nixpacks build . --name my-project locally it builds, however I am unable to start the application using docker run -it my-project as my apps don't seem to pick up the variables from the .env file. Moreover, when the project deploys to Railway, the project builds ok, but when it comes to serving the applications, astro defaults to serving at localhost. here is the problem line from the deploy logs: @project/web:start: 01:19:21 AM [@astrojs/node] Server listening on http://127.0.0.1:3001 - but I've set PUBLIC_URL to https://my-project-production.up.railway.app in the railway variables. Here are my relevant files to help understand where I'm going wrong:
61 Replies
Percy
Percy9mo ago
Project ID: 5c47585a-3af2-4850-9344-92e188bd6eea
donutmuncha
donutmuncha9mo ago
5c47585a-3af2-4850-9344-92e188bd6eea here is my ./turborepo.json:
{
"$schema": "https://turbo.build/schema.json",
"globalEnv": [
"MONGODB_URI",
"DB_NAME",
"PAYLOAD_PORT",
"PAYLOAD_PUBLIC_SERVER_URL",
"PAYLOAD_SECRET",
"PAYLOAD_CONFIG_PATH",
"PUBLIC_PORT",
"PUBLIC_URL",
"NODE_ENV"
],
"globalDotEnv": [".env"],
"pipeline": {
"build": {
"outputs": ["dist/**", "build/**"],
"dependsOn": ["^build"],
"inputs": ["src/**"]
},
"test": {},
"lint": {},
"dev": {
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["build"]
},
"start": {}
}
}
{
"$schema": "https://turbo.build/schema.json",
"globalEnv": [
"MONGODB_URI",
"DB_NAME",
"PAYLOAD_PORT",
"PAYLOAD_PUBLIC_SERVER_URL",
"PAYLOAD_SECRET",
"PAYLOAD_CONFIG_PATH",
"PUBLIC_PORT",
"PUBLIC_URL",
"NODE_ENV"
],
"globalDotEnv": [".env"],
"pipeline": {
"build": {
"outputs": ["dist/**", "build/**"],
"dependsOn": ["^build"],
"inputs": ["src/**"]
},
"test": {},
"lint": {},
"dev": {
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["build"]
},
"start": {}
}
}
./package.json
{
"name": "my project",
"description": "An Astro, PayloadCMS and turborepo project",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"prepare": "husky install",
"build": "turbo build",
"build:api": "dotenv -- turbo build --filter=api",
"build:web": "dotenv -- turbo build --filter=web",
"dev": "dotenv -- turbo dev",
"dev:api": "dotenv -- turbo dev --filter=api",
"dev:web": "dotenv -- turbo dev --filter=web",
"test": "turbo test",
"lint": "turbo lint",
"format": "prettier --write \"**/*.{ts,tsx,md,astro}\"",
"generate:types": "dotenv -- pnpm --filter=api generate:types ",
"start:api": "dotenv -- turbo start --filter=api",
"start:web": "dotenv -- turbo start --filter=web",
"start": "dotenv -- turbo start"
},
"devDependencies": {
"@turbo/gen": "^1.9.7",
"dotenv": "^8.2.0",
"dotenv-cli": "^7.2.1",
"eslint": "latest",
"eslint-config-custom": "*",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
"prettier": "latest",
"prettier-plugin-astro": "latest",
"turbo": "latest"
},
"packageManager": "pnpm@8.7.5",
"lint-staged": {
"apps/**/*.{js,ts,astro}": [
"eslint --fix"
],
"packages/**/*.{js,ts,astro}": [
"eslint --fix"
],
"*.json": [
"prettier --write"
]
}
}
{
"name": "my project",
"description": "An Astro, PayloadCMS and turborepo project",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"prepare": "husky install",
"build": "turbo build",
"build:api": "dotenv -- turbo build --filter=api",
"build:web": "dotenv -- turbo build --filter=web",
"dev": "dotenv -- turbo dev",
"dev:api": "dotenv -- turbo dev --filter=api",
"dev:web": "dotenv -- turbo dev --filter=web",
"test": "turbo test",
"lint": "turbo lint",
"format": "prettier --write \"**/*.{ts,tsx,md,astro}\"",
"generate:types": "dotenv -- pnpm --filter=api generate:types ",
"start:api": "dotenv -- turbo start --filter=api",
"start:web": "dotenv -- turbo start --filter=web",
"start": "dotenv -- turbo start"
},
"devDependencies": {
"@turbo/gen": "^1.9.7",
"dotenv": "^8.2.0",
"dotenv-cli": "^7.2.1",
"eslint": "latest",
"eslint-config-custom": "*",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
"prettier": "latest",
"prettier-plugin-astro": "latest",
"turbo": "latest"
},
"packageManager": "pnpm@8.7.5",
"lint-staged": {
"apps/**/*.{js,ts,astro}": [
"eslint --fix"
],
"packages/**/*.{js,ts,astro}": [
"eslint --fix"
],
"*.json": [
"prettier --write"
]
}
}
./apps/web/astro.config.ts
import { defineConfig } from "astro/config"
import nodejs from "@astrojs/node";
import sitemap from "@astrojs/sitemap"
import tailwind from "@astrojs/tailwind"
const astroPort = process.env.PUBLIC_PORT
const payloadPublicServerUrl = process.env.PAYLOAD_PUBLIC_SERVER_URL
const siteURL = process.env.PUBLIC_URL

const sitemapConfig = {i18n: {
defaultLocale: 'en',
locales: {
en: 'en-US',
es: 'es-ES',
},
},
}

export default defineConfig({

site: siteURL,
integrations: [tailwind(), sitemap(sitemapConfig)],
server: {
port: parseInt(astroPort ?? "3000"),
},
adapter: nodejs({
mode: "standalone",
}),
output: "hybrid",
vite: {
define: {
"import.meta.env.PAYLOAD_PUBLIC_SERVER_URL": JSON.stringify(
payloadPublicServerUrl,
),
},
},
});
import { defineConfig } from "astro/config"
import nodejs from "@astrojs/node";
import sitemap from "@astrojs/sitemap"
import tailwind from "@astrojs/tailwind"
const astroPort = process.env.PUBLIC_PORT
const payloadPublicServerUrl = process.env.PAYLOAD_PUBLIC_SERVER_URL
const siteURL = process.env.PUBLIC_URL

const sitemapConfig = {i18n: {
defaultLocale: 'en',
locales: {
en: 'en-US',
es: 'es-ES',
},
},
}

export default defineConfig({

site: siteURL,
integrations: [tailwind(), sitemap(sitemapConfig)],
server: {
port: parseInt(astroPort ?? "3000"),
},
adapter: nodejs({
mode: "standalone",
}),
output: "hybrid",
vite: {
define: {
"import.meta.env.PAYLOAD_PUBLIC_SERVER_URL": JSON.stringify(
payloadPublicServerUrl,
),
},
},
});
deployment log:
> dotenv -- turbo start

Packages in scope: @kanexa/api, @kanexa/web, eslint-config-custom

Running start in 3 packages

Remote caching disabled

@my-project/api:start: cache miss, executing b974cc07cd2a38cf

@my-project/web:start: cache miss, executing de0071a945aefc45

@my-project/web:start:

@my-project/web:start: > @kanexa/web@0.0.1 start /app/apps/web

@my-project/web:start: > node ./dist/server/entry.mjs

@my-project/web:start:

@my-project/api:start:

@my-project/api:start: > @kanexa/api@1.0.0 start /app/apps/api

@my-project/api:start: > pnpm run serve

@my-project/api:start:

@my-project/web:start: 01:19:21 AM [@astrojs/node] Server listening on http://127.0.0.1:3001

@my-project/api:start:

@my-project/api:start: > @kanexa/api@1.0.0 serve /app/apps/api

@my-project/api:start: > cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js

@my-project/api:start:

@my-project/api:start: [01:19:24] INFO (payload): Connected to MongoDB server successfully!

@my-project/api:start: [01:19:24] INFO (payload): Starting Payload...

@my-project/api:start: [01:19:25] WARN (payload): express.middleware is deprecated. Please migrate to express.postMiddleware.

@my-project/api:start: [01:19:25] INFO (payload): Payload Admin URL: http://kanexa-rebrand-production.up.railway.app/admin

ELIFECYCLE  Command failed.

@my-project/api:start:  ELIFECYCLE  Command failed.

@my-project/api:start:  ELIFECYCLE  Command failed.

@my-project/web:start:  ELIFECYCLE  Command failed.
> dotenv -- turbo start

Packages in scope: @kanexa/api, @kanexa/web, eslint-config-custom

Running start in 3 packages

Remote caching disabled

@my-project/api:start: cache miss, executing b974cc07cd2a38cf

@my-project/web:start: cache miss, executing de0071a945aefc45

@my-project/web:start:

@my-project/web:start: > @kanexa/web@0.0.1 start /app/apps/web

@my-project/web:start: > node ./dist/server/entry.mjs

@my-project/web:start:

@my-project/api:start:

@my-project/api:start: > @kanexa/api@1.0.0 start /app/apps/api

@my-project/api:start: > pnpm run serve

@my-project/api:start:

@my-project/web:start: 01:19:21 AM [@astrojs/node] Server listening on http://127.0.0.1:3001

@my-project/api:start:

@my-project/api:start: > @kanexa/api@1.0.0 serve /app/apps/api

@my-project/api:start: > cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js

@my-project/api:start:

@my-project/api:start: [01:19:24] INFO (payload): Connected to MongoDB server successfully!

@my-project/api:start: [01:19:24] INFO (payload): Starting Payload...

@my-project/api:start: [01:19:25] WARN (payload): express.middleware is deprecated. Please migrate to express.postMiddleware.

@my-project/api:start: [01:19:25] INFO (payload): Payload Admin URL: http://kanexa-rebrand-production.up.railway.app/admin

ELIFECYCLE  Command failed.

@my-project/api:start:  ELIFECYCLE  Command failed.

@my-project/api:start:  ELIFECYCLE  Command failed.

@my-project/web:start:  ELIFECYCLE  Command failed.
Brody
Brody9mo ago
does turbo start start everything?
donutmuncha
donutmuncha9mo ago
I think the key things im doing wrong are how I access environment variables in development and in railway. hence the nixpacks build not giving me the ability to serve locally (or on railway) locally yes: pnpm turbo start
Brody
Brody9mo ago
how many services are in this repo?
donutmuncha
donutmuncha9mo ago
at the moment just two: - payloadcms (./apps/api) utillising mongodb atlas via connections string in ENV - astro as nodejs standalone+hybrid mode
Brody
Brody9mo ago
payload is called api and web is the astro app?
donutmuncha
donutmuncha9mo ago
yes - api (payload) - web (astro) api because im using payload in localapi mode in my astro application
donutmuncha
donutmuncha9mo ago
project structure:
No description
donutmuncha
donutmuncha9mo ago
I actually used your payload template as a reference for the payload app (replaced dotenv with cross-env)
Brody
Brody9mo ago
okay, two services, so that means you need two railway services - each of these services will deploy from the same github repo - in one of the services settings youd set the build command to pnpm run build:api and the start command to pnpm run start:api this will be the payload service, so name the railway service appropriately - in the other service youd set a build command to pnpm run build:web and a start command to pnpm run start:web this is the web service, so name that appropriately too - as for astro listening on 127.0.0.1 instead of 0.0.0.0 like it needs to, you can server.host to 0.0.0.0 in your astro config file, like so https://github.com/brody192/astrowind-template/blob/main/astro.config.mjs#L38-L40
donutmuncha
donutmuncha9mo ago
ok, maybe I misunderstood how to do that part in the docs https://docs.railway.app/deploy/monorepo#shared-monorepo
Railway Docs
Monorepo Support | Railway Docs
Documentation for Railway
donutmuncha
donutmuncha9mo ago
I'll give that a go now
Brody
Brody9mo ago
yeah you need two services since these is no way to expose two apps with only one railway service
donutmuncha
donutmuncha9mo ago
I thought that may be the case. I was hoping otherwise, but it makes sense
Brody
Brody9mo ago
well if you want all access to come and go though one service that can be arranged, but that requires two services for your apps and then a proxy service like so https://railway.app/project/35d8d571-4313-4049-9699-4e7db7f02a2f I have a template for that too, but let's get your services running first
donutmuncha
donutmuncha9mo ago
happy to send you a coffee when done
Brody
Brody9mo ago
awh thank you 🙂
donutmuncha
donutmuncha9mo ago
ok, some progress. payload built successfully, and deployed, but I think I may need to add railway IP's to mongodb atlas. when navigating to my app, I get a blank white page seems I already have 0.0.0.0/0 (allow from anywhere). At this point it seems like I should also just provision a mongodb in railway.
Brody
Brody9mo ago
railway deployments have dynamic ips, so you cant whitelist any single one, you would need to whilelist all the cidr ranges for the region, heres them all for railways default region https://utilities.up.railway.app/cidr-list?value=us-west1 0.0.0.0/0 works too do you get any errors in the payload's deployment logs?
donutmuncha
donutmuncha9mo ago
no, I think I may have been hasty, I can hit the /admin endpoint, but it's complaining fetching user failed: failed to fetch which would mean it cant get the mongo
Brody
Brody9mo ago
do a quick test and disable private networking
donutmuncha
donutmuncha9mo ago
no joy
Brody
Brody9mo ago
hmmm not much going on here, can i see a screenshot of your service variables?
donutmuncha
donutmuncha9mo ago
No description
donutmuncha
donutmuncha9mo ago
I've set PORT to the same port as PAYLOAD_PORT assuming railway needs to expose it and not pick up the latter
Brody
Brody9mo ago
payload should listen on the auto assigned PORT, its must since i didnt define a PORT in my template, but you are getting a white screen so railway can access your app so thats good and your code is setup to read from the environment, and not just variables loaded from a .env file? you have the payload public server url set to your railway domain right?
donutmuncha
donutmuncha9mo ago
I'm able to hit payload and see a loading gif, so the app is running. apart from package.json which is virtually a clone of your own from the template, I load ENV's like this: ./apps/api/src/server.ts
import express from "express";
import payload from "payload";

const app = express();

// Redirect root to Admin panel
app.get("/", (_, res) => {
res.redirect("/admin");
});

const start = async () => {
// Initialize Payload
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: async () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
},
mongoOptions: {
dbName: process.env.DB_NAME,
},
});

// Add your own express routes here

app.listen(process.env.PAYLOAD_PORT ?? 3000);
};

start();
import express from "express";
import payload from "payload";

const app = express();

// Redirect root to Admin panel
app.get("/", (_, res) => {
res.redirect("/admin");
});

const start = async () => {
// Initialize Payload
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: async () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
},
mongoOptions: {
dbName: process.env.DB_NAME,
},
});

// Add your own express routes here

app.listen(process.env.PAYLOAD_PORT ?? 3000);
};

start();
Nothing in the app is referencing .env everything assumes its reading from process
Brody
Brody9mo ago
but admin is still white?
donutmuncha
donutmuncha9mo ago
No description
Brody
Brody9mo ago
can you send me the link? but not white, so progress!!
donutmuncha
donutmuncha9mo ago
let me know when you have it
Brody
Brody9mo ago
what do you have set for the payload url?
donutmuncha
donutmuncha9mo ago
the same link, but just http as opposed to https
Brody
Brody9mo ago
you want https railway is https only
donutmuncha
donutmuncha9mo ago
ok
Brody
Brody9mo ago
set it to this https://${{RAILWAY_PUBLIC_DOMAIN}} this will render out to the domain of the service so if you ever changed the domain or add a custom domain you wouldnt have to change the environment variable manually and youd only have to redepleoy the service for the new variable to take effect
donutmuncha
donutmuncha9mo ago
woop woop!! that did the trick where can I send you that coffee?
Brody
Brody9mo ago
if you insist, theres a link in my bio, you dont have to though
donutmuncha
donutmuncha9mo ago
I spent a week smashing my head trying to deploy on vercel, no one responded in community forums. came here and within a couple of hours you've helped me out. more than happy to oblige. you've really helped me out thank you done. coffee fund topped up 🙌🏾
Brody
Brody9mo ago
thank you so much 🙂 we still got that astro app to tackle though!
donutmuncha
donutmuncha9mo ago
its 4:43am here, (GMT+0) fading slowly!
Brody
Brody9mo ago
no worries, we can pick this back up another time! (im EST)
donutmuncha
donutmuncha9mo ago
the truth is I haven't even started to build the astro frontend yet, I have been going in circles just trying to get this up and running
Brody
Brody9mo ago
yeah best to just tackle one service/app at a time
donutmuncha
donutmuncha9mo ago
but, I have a /health endpoint, so if I can hit that, then I can try figure out the localapi stuff after I've set it all up in railway, how can I trigger a redeployment? I cancelled the first
Brody
Brody9mo ago
would also be a good idea to have railway hit the healthcheck too! https://docs.railway.app/deploy/healthchecks
donutmuncha
donutmuncha9mo ago
yep, set that up
Brody
Brody9mo ago
you can give this a shot https://bookmarklets.up.railway.app/service-redeploy/ but due to limitations with railways api it doesnt always work as intended, otherwise just disable the git deploy trigger in the settings and then connect enviroment to branch i have been begging railway to slap in a "deploy now" button for a long time
donutmuncha
donutmuncha9mo ago
I could do this and push to my repo:
git commit --allow-empty -m "This will trigger a build"
git commit --allow-empty -m "This will trigger a build"
Brody
Brody9mo ago
oh yeah that works too forgot about that trick
donutmuncha
donutmuncha9mo ago
I always forget, had to turn it into a gist https://gist.github.com/Demwunz/6be3f3929fc56ad84404970310c51347
celebrations
celebrations9mo ago
this shuold be pinned
Brody
Brody9mo ago
that is usefull
donutmuncha
donutmuncha9mo ago
I'll be writing all the above up into a tutorial and sticking it online soon. It's the only way I'll remember how to do it again
Brody
Brody9mo ago
thats a pretty smart way of doing things!
donutmuncha
donutmuncha9mo ago
ok, astro deployed, I got a response back from /health. I'll build out some pages tomorrow. The next thing will be trying to connect to payload via localapi (https://payloadcms.com/docs/local-api/overview), but thats a different issue to tackle. Thanks again @Brody you've been amazing.
Brody
Brody9mo ago
no problem! happy to help!