Ambiorix on Posit Workbench (server)
I love the idea of ambiorix and wanted to play around with it.
We are using Posit Workbench at work, I used it to play around, but ran into some difficulties. I thought I could document the topics here, maybe there are some small fixes to make everything run smoothly or maybe there are some bits that can be added to the documentation to make the DX with the Posit suite nicer.
- When using the default host, it didnt work, changing the host to "0.0.0.0" didnt work, but "127.0.0.1" worked.
Similarly, I used
host = system("hostname -I", intern = TRUE)but this raised an error, as there is an extra space behind (addingtrimwsfixed it). By default the app started in the Viewer panel, which didnt work, but opening it in an extra window helped.
library(ambiorix)
app <- Ambiorix$new(port = 8000L) # fails
app <- Ambiorix$new(port = 8000L, host = "0.0.0.0") # fails
app <- Ambiorix$new(port = 8000L, host = "127.0.0.1") # works
app <- Ambiorix$new(port = 8000L, host = trimws(system("hostname -I", intern = TRUE))) # works
app$get("/", \(req, res) res$send("Hello, World!"))
app$start()
-
Redirects don't work when the app starts from workbench. Eg when I use the Posit Conf 2025 sample app, and click on any app, it starts on
<URL>.com/s/<SESSION_ID>/p/<HASH>/, when I click on any link, egdatasets/mtcars, it redirects me to<URL>.com/datasets/mtcarsand not<URL>.com/s/<SESSION_ID>/p/<HASH>/datasets/mtcars. Not sure if this is something that can be fixed on the Workbench's settings or from the R/package's side. -
I couldn't figure out deploying it to Posit Connect; I tried it with
rsconnect::deployAPI, added anentrypoint.Rwhich sources theapp.R. I see in the logs that the app is listening on http://127.0.0.1:3000, but this is not the information that connect expects, I guess. I tried to play around with host and port, but could not find the right combination.
I am happy to discuss, run some tests, or do something else if that helps!
Thank you so very much for this detailed issue David! It really helps a ton. I thought we had documented how to deploy to Connect, it's documented for shinyapps.io and I believe I managed to make it work on Connect using the same method, the key there is: port <- Sys.getenv("SHINY_PORT").
Also note that you will need the comment below at the top of your app.R if you want Workbench to display the deploy and run button, though I might be mistaken.
# Launch the ShinyApp (Do not remove this comment)
Thank you again for the much detailed issue, please try the above to deploy if you still have some time to spare.
Happy to debug this one for you!
At the moment I have this in app.R
library(ambiorix)
app <- Ambiorix$
new(
port = Sys.getenv("SHINY_PORT"),
host = trimws(system("hostname -I", intern = TRUE))
)$
get(
"/",
\(req, res) {
res$send("Hello, World!")
}
)$
start()
I then deploy the app with
rsconnect::deployApp(
appName = "ambiorix-minimal-app",
appFiles = "app.R",
server = RSCONNECT_SERVER
)
The first thing I notice is, that it installs shiny as well (it detects it as a shiny app and thus it needs to be installed, not ideal but something I can live with, this is even more so if I have this on top # Launch the ShinyApp (Do not remove this comment) ). The site loads, never shows anything, and crashes after 60 secs.
2025/09/15 2:28:32 PM: Shiny application starting ...
2025/09/15 2:28:32 PM: ✔ 15-09-2025 12:28:32 Listening on http://<IP>:46785
2025/09/15 2:29:31 PM: [rsc-session] Received signal: interrupt
2025/09/15 2:29:31 PM: [rsc-session] Terminating subprocess with interrupt ...
2025/09/15 2:29:32 PM:
2025/09/15 2:29:32 PM:
2025/09/15 2:29:32 PM: ✖ 15-09-2025 12:29:32 Server stopped
2025/09/15 2:29:32 PM: Execution halted
when I deploy the app as an API using
rsconnect::deployAPI(
api = ".",
appName = "ambiorix-minimal-api",
appFiles = c("app.R", "entrypoint.R")
,
server = RSCONNECT_SERVER
)
Note I needed to change the port to
port <- Sys.getenv("PLUMBER_PORT")
if (port == "") port <- NULL
app <- Ambiorix$
new(
port = port,
...
I can see that the api was started, but it also fails after 60 secs as well
2025/09/15 2:31:38 PM: Plumber API starting ...
2025/09/15 2:31:38 PM:
2025/09/15 2:31:38 PM: Attaching package: ‘ambiorix’
2025/09/15 2:31:38 PM:
2025/09/15 2:31:38 PM: The following object is masked from ‘package:plumber’:
2025/09/15 2:31:38 PM:
2025/09/15 2:31:38 PM: forward
2025/09/15 2:31:38 PM:
2025/09/15 2:31:38 PM: ✔ 15-09-2025 12:31:38 Listening on http://<IP>:24574
2025/09/15 2:32:37 PM: [rsc-session] Received signal: interrupt
2025/09/15 2:32:37 PM: [rsc-session] Terminating subprocess with interrupt ...
2025/09/15 2:32:38 PM:
2025/09/15 2:32:38 PM:
2025/09/15 2:32:38 PM: ✖ 15-09-2025 12:32:38 Server stopped
2025/09/15 2:32:38 PM: Execution halted
2025/09/15 2:32:38 PM: [rsc-session] Terminated subprocess with signal: interrupt
Happy to try something else!
I got a hold of a Connect instance via a client.
The application below is deployable via the Publish button from Worbench (thanks to the first line comment the button appears)
# Launch the ShinyApp (Do not remove this comment)
library(ambiorix)
app <- Ambiorix$
new(
port = Sys.getenv("SHINY_PORT"),
host = "127.0.0.1"
)$
get(
"/",
\(req, res) {
res$send("Hello, World!")
}
)$
start()
I can also deploy it with rsconnect::deployApp() leaving all arguments on default.
That works like a charm.
What I'm still missing is redirects.
Let's say I have the following app:
# Launch the ShinyApp (Do not remove this comment)
library(ambiorix)
library(htmltools)
app <- Ambiorix$
new(
port = Sys.getenv("SHINY_PORT"),
host = "127.0.0.1"
)$
get(
"/",
\(req, res) {
html <- tagList(
tags$h1("Hello World"),
tags$a("Goto /hello", href = "/hello")
)
res$send(html)
}
)$
get(
"/hello",
\(req, res) {
res$send("Hello from /hello")
}
)$
start()
and I click on the redirect to /hello, I get a "The requested page was not found" (posit workbench, dev), or a "404 page not found" (posit connect, prod).
This is, because the URL redirect goes to the highest level and does not carry enough of the information along:
- workbench: base app
https://URL>/s/<SESSION_ID>/p/<PORT_ID>/-> hello redirect should go tohttps://URL>/s/<SESSION_ID>/p/<PORT_ID>/hellobut goes tohttps://URL>/hello - connect: base app
https://<URL>/content/<CONTENT_ID>/-> hello redirect should go tohttps://<URL>/content/<CONTENT_ID>/hellobut goes tohttps://<URL>/hello
ahh, changing the redirect from href = "/hello" to href = "hello" makes it work.
Looking at the Sample Posit App, I see that href = "/api" (source) instead of href = "api". Is there some way that it can be unified?
Is it maybe possible to have a section on Posit Workbench/Connect in the deploy documentation at https://ambiorix.dev/docs/ambiorix/deploy/?
I think, I haven't tried, but what we have to do is change the host to https://<URL>/content/<CONTENT_ID>/ but, of course, it isn't known at build time, it's only known after the first deploy. Some Connect instances allow creating a custom root /path.
I think this will then fix this issue as links from root e.g.: /hello will start at https://<URL>/content/<CONTENT_ID>/ instead of the root of the server https://<URL>.
I will give it a go later today.
Thank you again for your help looking into this David!
@kennedymwavu this is a bit nasty here, I'm not sure how we document and, ideally, handle that better within ambiorix. It looks difficult to support Connect without breaking support for other platforms :)
we can probably grab the content ID via the env var CONNECT_CONTENT_GUID during runtime and use same strategy i talked about in this comment https://github.com/ambiorix-web/ambiorix/issues/119#issuecomment-2734288385
i concur we should add some docs on this, but not exactly sure if we'd have to change anything on ambiorix itself.
I played around a bit and I see on Posit Connect the following ENV variables, which could be used to create the host:
CONNECT_CONTENT_GUID -> "<CONTENT_ID>"
CONNECT_SERVER -> "https://<URL>/"
RSTUDIO_PRODUCT and POSIT_PRODUCT -> "CONNECT"
on Posit Workbench
RS_SERVER_ADDRESS -> "https://<URL>/"
RS_SESSION_URL -> "/s/<SESSION_ID>"
the only thing I couldn't find is the /p/<PORT> part of the URL
A further complication that came to mind when we use href = "hello" (instead of href = "/hello"), further links or going back dont work.
Consider the following example, where we cannot reach the main page again once we visited /hello
app <- Ambiorix$
new(
port = Sys.getenv("SHINY_PORT"),
# host = get_host()
host = "127.0.0.1"
)$
get(
"/",
\(req, res) {
html <- tagList(
tags$h1("Hello World"),
tags$a("Goto /hello", href = "hello") # "hello" instead of "/hello" due to the host issue
)
res$send(html)
}
)$
get(
"/hello",
\(req, res) {
html <- tagList(
tags$h1("Hello World"),
tags$a("Go back home to /", href = "") # this fails... should be "/"
# "/", "", or ".." fail as well
)
res$send(html)
}
)$
start()
Can you try ../hello and ./?
that works, it feels a bit weird to use when I have multiple levels in the URL (eg mysite.com/listings/:id), but this should be a good workaround for "the posit issue" for the moment...
app <- Ambiorix$
new(
port = Sys.getenv("SHINY_PORT"),
# host = get_host()
host = "127.0.0.1"
)$
get(
"/",
\(req, res) {
html <- tagList(
tags$h1("My super Store"),
tags$ul(
tags$li(tags$a("Goto /listings/1", href = "./listings/1")),
tags$li(tags$a("Goto /listings/2", href = "./listings/2"))
)
)
res$send(html)
}
)$
get(
"/listings/:id",
\(req, res) {
html <- tagList(
tags$h1("Listing", req$params$id),
tags$a("Go back home to /", href = "../") # a bit annoying but ok
# "/", "", or ".." fail as well
)
res$send(html)
}
)$
start()