Same project, public url working and private url NOT working

lozingaro
PRO

2 months ago

React "frontend" service with VITE "api" backend conf in the same project.

VITE_API_BASE_URL=https://${{api.RAILWAY_PUBLIC_DOMAIN}} # working fine (egress!!)
VITE_API_BASE_URL=https://${{api.RAILWAY_PRIVATE_DOMAIN}} # not working: cannot resolve
$10 Bounty

8 Replies

Railway
BOT

2 months ago

Hey there! We've found the following might help you get unblocked faster:

If you find the answer from one of these, please let us know by solving the thread!


Railway

Hey there! We've found the following might help you get unblocked faster: - [🧵 PostgreSQL Private Connection Not Resolving (RAILWAY_PRIVATE_DOMAIN Fails with NXDOMAIN)](https://station.railway.com/questions/postgre-sql-private-connection-not-resol-1a42435e) If you find the answer from one of these, please let us know by solving the thread!

lozingaro
PRO

2 months ago

Not applicabel in this case, service is named "api" and the reference is correct (at least is the one referenced by Railway itself) and the working/not working happens connecting to frontend railway url, not local shell.


kristijan1996
HOBBY

2 months ago

I am struggling with the same thing.

I have been trying for days now to connect my backend Flask with frontend Vue.

Using VITE_API_BASE_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}} in your frontend service (@lozingaro you have to set PORT manually in your api service Variables. That's what the docs say. Also note that it is http not https ) does not work at all. Vite bakes its env vars in build time, so you have to manually expose VITE_API_BASE_URL before npm run build in order for it to even work. And as far as I can see, service Variables don't get picked up during build using Dockerfile so I had to explicitly expose

ENV VITE_BACKEND_URL="http://backend.railway.internal:8000"
ENV VITE_LOGGER_LEVEL="debug"
# Build the app.
RUN npm run build

in my Dockerfile which is crazy.

Nevertheless, even if you do this, the first issue you will encounter here is mixed resources, i.e. you cannot go to your browser to a https frontend link and then have frontend call http api endpoint (browsers don't allow it for security reasons). So that doesn't work.

I tried setting up reverse proxy in Caddyfile to route all /api/* calls to my backend's private domain:port, but no luck there as well.

What is weird is that you can ssh into your frontend service using railway ssh and using wget you can hit a backend endpoint and have it return proper response.

I guess that leaves us only with doing fe-be communication through backend's public address.

This problem is driving me crazy. Can someone from dev team explain to us what the proper way is to connect your frontend and backend services please? I do get that my backend will communicate to my postgres DB through internal network, but I don't get why I have to expose backend through the public URL in order for my frontend (which is in the same project, one of the 3 services i use) to be able to communicate to it.


kristijan1996

I am struggling with the same thing. I have been trying for days now to connect my backend Flask with frontend Vue. Using VITE_API_BASE_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}} in your frontend service (@lozingaro you have to set PORT manually in your api service Variables. That's what the docs say. Also note that it is http not https ) does not work at all. Vite bakes its env vars in build time, so you have to manually expose VITE_API_BASE_URL before npm run build in order for it to even work. And as far as I can see, service Variables don't get picked up during build using Dockerfile so I had to explicitly exposeENV VITE_BACKEND_URL="http://backend.railway.internal:8000" ENV VITE_LOGGER_LEVEL="debug" # Build the app. RUN npm run buildin my Dockerfile which is crazy.Nevertheless, even if you do this, the first issue you will encounter here is mixed resources, i.e. you cannot go to your browser to a https frontend link and then have frontend call http api endpoint (browsers don't allow it for security reasons). So that doesn't work.I tried setting up reverse proxy in Caddyfile to route all /api/* calls to my backend's private domain:port, but no luck there as well.What is weird is that you can ssh into your frontend service using railway ssh and using wget you can hit a backend endpoint and have it return proper response. I guess that leaves us only with doing fe-be communication through backend's public address.This problem is driving me crazy. Can someone from dev team explain to us what the proper way is to connect your frontend and backend services please? I do get that my backend will communicate to my postgres DB through internal network, but I don't get why I have to expose backend through the public URL in order for my frontend (which is in the same project, one of the 3 services i use) to be able to communicate to it.

lozingaro
PRO

2 months ago

Thank you for your reply!

As you can see from my config, i set https and not http. So, the mixed content is not my concern at this time. For the time being, I am using public railway domain dns for communicating frontend-backend. Same thing goes for setting allowed origins in backend, i had to go public instead of private.

In principle, i do not care too much since it is working, but the feature of having and internal routing is pricelesse, literally


lozingaro
PRO

2 months ago

Something interesting is that communication backend-postgresql-db is working with internal adress.


kristijan1996
HOBBY

2 months ago

So, the public domain will work no doubt, the only downside is that it incurs traffic cost.

When you mentioned VITE_API_BASE_URL=https://${{api.RAILWAY_PRIVATE_DOMAIN}} # not working: cannot resolve
it immediately seemed like the problem I was dealing with too.

In my case, as it is in yours, Backend to PostgresDB communication is working fine using Private networking . You can connect backend to postgres using

provided PGHOST, PGPORT, PGUSER and PGPASSWORD variables from postgres service.

But the Frontend to Backend communication can also be set to use private networking using Caddyfile . That's how I ended up solving the following problem:

  • If you try to bake in

    VITE_API_BASE_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}

    as prefix for your backend routes your frontend is calling, for example by prefixing /api/some-endpoint with ${VITE_API_BASE_URL} , your fetch or axios.get/post call will hit

    http:://<YOUR-BACKEND-SERVICE-NAME>.railway.internal:<PORT-ENV-VAR-FROM-BACKEND-SERVICE>/api/some-endpoint

    and you will get Mixed content error in browser because a https frontend route is calling an insecure http backend route

  • For internal networking, we must use http since Railway's internal network doesn't support https (it is already secure)

  • So you resolve to using VITE_API_BASE_URL=https://${{api.RAILWAY_PUBLIC_DOMAIN}} which is https but incurs costs. This will work.

  • In order to use private networking and circumvent the Mixed content error you have to have a Caddyfile (you can start off with the one used in docs for deploying a solo Vue app)

    • You need to rewrite it as following in order for it to reroute all /api/ calls to your backend service, while serving dist/ + index.html to all other paths (which is what you want for frontend routes)

      # global options
      {
          admin off # there's no need for the admin api in railway's environment
          persist_config off # storage isn't persistent anyway
          auto_https off # railway handles https for us, this would cause issues if left enabled
          # runtime logs
          log {
              format json # set runtime log format to json mode
          }
          # server options
          servers {
              trusted_proxies static private_ranges 100.0.0.0/8 # trust railway's proxy
          }
      }
      
      # site block, listens on the $PORT environment variable, automatically assigned by railway
      :{$PORT:3000} {
          log {
              format json
          }
      
          # health check for railway
          rewrite /health /*
      
          # API routes → backend
          handle /api/* { # we can use handle_route here instead if we wish to cut off the /api/ prefix
              reverse_proxy {$VITE_API_BASE_URL}
          }
      
          # Frontend routes → dist + index.html
          handle {
              # serve from the 'dist' folder (Vite builds into the 'dist' folder)
              root * dist
      
              # enable gzipping responses
              encode gzip
      
              # serve files from 'dist'
              file_server
      
              # if path doesn't exist, redirect it to 'index.html' for client side routing   
              try_files {path} /index.html
          }
      }
    • Set PORT variable in your backend service to something like 8000

    • Serve backend with something like (make it listen on ${PORT} env var)

      CMD gunicorn --bind [::]:${PORT:-3000} wsgi:application
    • Now you can set

      VITE_API_BASE_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}

      in your Frontend env, which will be picked up by the Caddyfile and you are good to go


lozingaro
PRO

2 months ago

Thanks again, but I do not get why I should add this complexity layer such as a reverse proxy.

This looks like a workaround and not a solution. Private DNS should route connections to backend seamlessly.

I think you’ll agree to that.

Bests.


fra
HOBBYTop 10% Contributor

2 months ago

The internal url works only if the request is server side.

If you are calling the backend from the client side it will never work with the internal url, you need to use the public url or you need to create a proxy to the backend like a bff (backend for frontend) where you expose a /api url that forward the request to the backend.

The fact that you need this variable at build time it means you are using it client side, otherwise it would read it directly from process.env


Same project, public url working and private url NOT working - Railway Help Station