<@ULVA73B9P> I'm using LocalForward 14200 localhos...
# ask-marvin
l
@Marvin I'm using LocalForward 14200 localhost:4200 in my configured SSH host so I can access the GUI of my server's prefect. I'm running the prefect API on my local as well on the 4200, that's why I use a different port (14200). When I go to localhost:14200 I see exactly the same deployments and runs from my local. To confirm, I stopped the prefect-server and prefect-worker containers and localhost:14200 just shows a gray screen because it's actually trying to connect to my local localhost:4200 API instead of the remote. How to fix it?
m
thought for 138.9 seconds
What’s happening is that the UI is being told to use
localhost:4200
as its API URL
, so when you open the forwarded UI on
localhost:14200
, the browser then makes API calls to your own machine’s
localhost:4200
. Fix: configure the remote Prefect server to advertise the correct API URL to the UI. What to set on the remote server Set:
Copy code
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
Then restart the remote Prefect server. If you’re using Docker Compose, that would look something like:
Copy code
yaml
environment:
  PREFECT_UI_API_URL: <http://localhost:14200/api>
Why this works: - you open the UI at
<http://localhost:14200>
- that request is SSH-forwarded to the remote server - the UI asks the server for
/ui-settings
- the remote server responds with the configured
api_url
- if that
api_url
is
<http://localhost:14200/api>
, your browser will send API requests through the same SSH tunnel instead of to your local Prefect instance on
4200
Important note -
localhost
in
PREFECT_UI_API_URL
is interpreted by the browser, not the remote machine - so in your case,
<http://localhost:14200/api>
is exactly what you want How to verify From your local machine:
Copy code
bash
curl <http://localhost:14200/ui-settings>
You should see JSON with:
Copy code
json
{
  "api_url": "<http://localhost:14200/api>"
}
If it still says
<http://localhost:4200/api>
, the remote server is still advertising the wrong value. If you use a config file instead of env vars In
prefect.toml
on the remote server:
Copy code
toml
[server.ui]
api_url = "<http://localhost:14200/api>"
Then restart the server. Related settings -
PREFECT_UI_API_URL
/ `PREFECT_SERVER_UI_API_URL`: explicit UI API URL - if unset, Prefect may fall back to
PREFECT_API_URL
or server host/port defaults, which often end up as
<http://localhost:4200/api>
Docs/settings references: - Prefect server guide - Settings reference If you want, I can help you write the exact
docker-compose.yml
change for your current Prefect server container.
l
@Marvin I have this in a .env:
PREFECT_API_URL=<http://127.0.0.1:4200/api>
Could I keep it the same and just add a new
Copy code
PREFECT_UI_API_URL=<http://localhost:14200/api>
? Would it still work?
m
thought for 15.1 seconds
Yes — that can work, with one important distinction: -
PREFECT_API_URL
is used by Prefect clients/workers/CLI -
PREFECT_UI_API_URL
is what the browser UI uses So you can keep:
Copy code
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
and add:
Copy code
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
That means: - processes running on the remote server still talk to the API at
127.0.0.1:4200
- your browser visiting the forwarded UI at
localhost:14200
will call the API through the SSH tunnel at
localhost:14200/api
That’s actually a good setup for your use case. A couple of cautions: 1. Don’t include angle brackets in the actual
.env
Use:
Copy code
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
not:
Copy code
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
2. Set
PREFECT_UI_API_URL
on the remote server container/process
It needs to be available to the Prefect server that serves the UI. 3. Restart the server after changing it 4. Verify with:
Copy code
bash
curl <http://localhost:14200/ui-settings>
You should see:
Copy code
json
{
  "api_url": "<http://localhost:14200/api>"
}
If you want, paste your Compose service/env block and I’ll sanity-check exactly where those vars should go.
l
@Marvin I followed the steps and changed the forward to
LocalForward 14200 localhost:14200
When I go to http://localhost:14200/dashboard I see a gray screen. This: http://localhost:14200/ui-settings returns a
ERR_CONNECTION_RESET
.
m
thought for 10.9 seconds
That change is the problem.
LocalForward 14200 localhost:14200
means: - local port
14200
- forwarded to remote
localhost:14200
But your Prefect server is listening on remote
4200
, not
14200
. So the tunnel is now pointing at the wrong remote port, which explains: - gray screen -
/ui-settings
connection reset What you want instead Keep the SSH forward as:
Copy code
ssh-config
LocalForward 14200 localhost:4200
That means: - local
localhost:14200
-> remote
localhost:4200
Then on the remote Prefect server, set:
Copy code
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
This is correct because: - remote server processes use
127.0.0.1:4200
- your local browser uses
localhost:14200
Why
PREFECT_UI_API_URL=<http://localhost:14200/api>
is okay
Even though it’s configured on the remote server, that value is consumed by the browser, so
localhost:14200
refers to your local machine, which is exactly where the SSH tunnel is listening. Recommended setup SSH config:
Copy code
ssh-config
Host my-prefect-server
  HostName <http://your.server.com|your.server.com>
  User your-user
  LocalForward 14200 localhost:4200
Remote `.env`:
Copy code
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
Then restart the remote Prefect server container/process. Checks to run 1. On the remote machine, confirm Prefect is actually listening on 4200:
Copy code
bash
curl <http://127.0.0.1:4200/api/health>
2. On your local machine, after reconnecting SSH:
Copy code
bash
curl <http://localhost:14200/ui-settings>
You should get JSON back. 3. Also test:
Copy code
bash
curl <http://localhost:14200/api/health>
If that works, the tunnel is good. One subtle issue to watch for If the UI loads from
localhost:14200
and then calls
<http://localhost:14200/api>
, that should work fine through the same tunnel. If it still doesn’t, the server may not have picked up
PREFECT_UI_API_URL
, or there may be a browser cache issue. A hard refresh/incognito window can help. If you want, I can help you debug this step-by-step from: - your SSH config - your docker compose ports/env - the output of
curl <http://127.0.0.1:4200/api/health>
on the remote - the output of
curl <http://localhost:14200/ui-settings>
locally
l
@Marvin curl http://127.0.0.1:4200/ui-settings {"api_url":"http://127.0.0.1:4200/api","csrf_enabled":false,"auth":null,"flags":[]}*%* As you can see, it's still pointing to the local 4200, not the remote.
m
thought for 11.5 seconds
127.0.0.1:4200
there is the remote server’s loopback, because you ran the
curl
on the remote host. So that output alone does not mean it’s pointing at your laptop. The real issue is that your server has not picked up
PREFECT_UI_API_URL
, because
/ui-settings
is still advertising:
Copy code
json
"api_url":"<http://127.0.0.1:4200/api>"
It should instead advertise:
Copy code
json
"api_url":"<http://localhost:14200/api>"
Also: your output shows angle brackets in the URL:
Copy code
json
"<http://127.0.0.1:4200/api>"
Actually your pasted command/output suggests you may literally have values like:
Copy code
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
If so, that is wrong for
.env
files. The values must be plain URLs, no
< >
. Use exactly:
Copy code
bash
PREFECT_API_URL=<http://127.0.0.1:4200/api>
PREFECT_UI_API_URL=<http://localhost:14200/api>
Next steps 1. Fix the
.env
values to remove angle brackets. 2. Make sure the vars are applied to the Prefect server container/process, not just somewhere else. 3. Restart/recreate the server container, not just the worker. For Docker Compose, usually:
Copy code
bash
docker compose up -d --force-recreate prefect-server
or if service names differ, recreate the service that serves the API/UI. 4. Re-check on the remote host:
Copy code
bash
curl <http://127.0.0.1:4200/ui-settings>
You want:
Copy code
json
{"api_url":"<http://localhost:14200/api>", ...}
Important detail If this is a containerized setup, editing a host
.env
file is only useful if that file is actually referenced by the container via: -
env_file:
- variable substitution in
docker-compose.yml
- or exported into the process environment before startup So also inspect the running container env. For example:
Copy code
bash
docker inspect <prefect-server-container> --format '{{range .Config.Env}}{{println .}}{{end}}' | grep PREFECT
You should see:
Copy code
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
If you don’t, the server container never received it. Likely diagnosis Most likely one of these is true: -
PREFECT_UI_API_URL
is not actually set in the server container - the container wasn’t recreated after changing
.env
- the
.env
contains invalid bracketed values like
<http://...>
If you paste your
docker-compose.yml
service for Prefect server, I can tell you exactly where the variable needs to go.
l
@Marvin
PREFECT_UI_API_URL
is not actually set in the server container. I added to the
.env
file only. It should be taken because I do this on the
prefect-server
container yaml:
Copy code
env_file:
      - .env
m
thought for 13.3 seconds
If
docker inspect
shows
PREFECT_UI_API_URL
is not in the running
prefect-server
container, then the container did not get that variable, even if
env_file: .env
is in the Compose YAML. Most likely causes: 1. the container was not recreated after the
.env
change 2. the wrong
.env
file is being loaded 3. the variable is malformed in
.env
4. the service you’re checking is not actually the one serving Prefect UI/API First: make sure the
.env
line is exactly this
Copy code
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
No quotes needed, and no angle brackets. Then recreate the service, not just restart it:
Copy code
bash
docker compose up -d --force-recreate prefect-server
A plain restart often won’t refresh env from
env_file
. Then check again:
Copy code
bash
docker inspect <prefect-server-container> --format '{{range .Config.Env}}{{println .}}{{end}}' | grep PREFECT_UI_API_URL
You should see:
Copy code
bash
PREFECT_UI_API_URL=<http://localhost:14200/api>
If you still don’t see it, verify Compose is reading the file you think it is:
Copy code
bash
docker compose config
That shows the fully rendered config. Confirm: - the
prefect-server
service has
env_file:
- it points to the correct path - there isn’t another compose file overriding it You can also inspect the rendered env section more directly with:
Copy code
bash
docker compose config | sed -n '/prefect-server:/,/^[^[:space:]]/p'
Another subtle but important distinction Docker Compose has: - project-level
.env
for variable substitution in compose files -
env_file:
for injecting env vars into the container These are related but not the same thing. In your case, since you explicitly use:
Copy code
yaml
env_file:
  - .env
the file should be injected into the container if the service is recreated. Quick sanity test Temporarily add something obvious to `.env`:
Copy code
bash
FOO_TEST=hello
Recreate:
Copy code
bash
docker compose up -d --force-recreate prefect-server
Then inspect:
Copy code
bash
docker inspect <prefect-server-container> --format '{{range .Config.Env}}{{println .}}{{end}}' | grep FOO_TEST
- if it appears, your
env_file
is working and the issue is specific to the Prefect var/value - if it does not appear, the service is not using the
env_file
you think it is After it is present in the container, verify:
Copy code
bash
curl <http://127.0.0.1:4200/ui-settings>
Expected:
Copy code
json
{"api_url":"<http://localhost:14200/api>", ...}
Then locally, with SSH forward set back to:
Copy code
ssh-config
LocalForward 14200 localhost:4200
test:
Copy code
bash
curl <http://localhost:14200/ui-settings>
curl <http://localhost:14200/api/health>
If you want, paste: - the
prefect-server
section of your
docker-compose.yml
- the exact
.env
lines for the two Prefect vars - the output of
docker compose config | sed -n '/prefect-server:/,/^[^[:space:]]/p'
and I’ll pinpoint what’s wrong.