App lifecycle
This page explains the full lifecycle of an app running in ShinyProxy. It can help you understand how ShinyProxy works and how to correctly configure the many configuration options that ShinyProxy provides.
An app has a specific status, which changes throughout the lifetime of the app. In most cases, the status of the app changes as follows:
The ShinyProxy UI uses the same colors to display these statuses. The following sections explain every status in more depth.
New
A user of ShinyProxy can start an app using one of the following methods:
- by clicking on the app on the main page
- by directly browsing to the URL of the app
- by using the switch instance modal
- by using the API
All these methods use the same procedure for starting the app. While the app is
starting, a loading screen is shown. In the background, ShinyProxy creates a new
app which has the New
status. The full startup procedure is:
- validate and process any parameters
- parse and resolve any SpEL expressions
- pull the container image, adhering to the configured image-pull-policy on Docker and Kubernetes
- create a container for the app using one of the container back-ends
- wait for the container to be running
- wait for the app to be reachable
As soon as all steps are completed, the app is ready to be used. ShinyProxy now hides the loading screen and the interface of the app is shown. If any off these steps fails, the procedure is aborted and the user is informed that the app failed to load. The exact reason is logged in the ShinyProxy logs. Step 1, 2, 3, and 4 fail as soon as something goes wrong. However, in step 5 and 6, ShinyProxy needs to wait for the external container backend (e.g. Kubernetes) to process the action. Therefore, ShinyProxy regularly checks the state of the container and the app. Both steps have a timeout which can be configured in the ShinyProxy config.
Step 5 waits for the container to be running. Note that when using the Docker backend, the container is created immediately and there is no waiting step. When this step takes more than 5 seconds, ShinyProxy starts logging the process, using one of the following log messages:
# Docker Swarm
2025-06-02T16:00:00.00+02:00 INFO 90611 --- [ProxyService-16] e.o.c.b.docker.DockerSwarmBackend : [user=jack proxyId=79b20724-eca6-4789-a974-e63c0e2ffc3d specId=demo] Waiting: Docker Swarm Service (11/40)
# Kubernetes
2025-06-02T16:00:00.00+02:00 INFO 90611 --- [ProxyService-16] e.o.c.b.kubernetes.KubernetesBackend : [user=jack proxyId=79b20724-eca6-4789-a974-e63c0e2ffc3d specId=demo] Waiting: Kubernetes Pod (11/40)
# ECS
2025-06-02T16:00:00.00+02:00 INFO 90611 --- [ProxyService-16] e.o.c.b.ecs.EcsBackend : [user=jack proxyId=79b20724-eca6-4789-a974-e63c0e2ffc3d specId=demo] Waiting: ECS Task (11/40)
This step has a timeout, which can be changed using the following configuration properties (click on the property for more information):
Backend | Property |
---|---|
Docker | N/A |
Docker Swarm | proxy.docker.service-wait-time |
Kubernetes | proxy.kubernetes.pod-wait-time |
ECS | proxy.ecs.service-wait-time |
When the timeout is reached, the app (including the container) is stopped and the user is informed that the app has failed to start.
Step 6 waits for the app itself to be reachable. ShinyProxy checks
this by sending HTTP requests to the app, it does this until a
successful response is received. For example, in the case of a Shiny app, the R
process is started by the container engine. R first loads all libraries and code
of the app. At this time, the Shiny webserver isn’t yet running, therefore
ShinyProxy still shows the loading screen. After a few seconds, the app is
available at port 3838
and the logs of the Shiny app logs
Listening on http://0.0.0.0:3838
. Once the webserver is running, ShinyProxy
receives a proper HTTP response while checking the reachability of the app, it
therefore hides the loading screen and shows the app to the user.
ShinyProxy assumes that a response having one of the following status codes is
successful: 200, 301, 302, 303, 307, 308
. When this step takes more than 5
seconds, ShinyProxy starts logging the process, using the following log message:
2025-06-02T16:00:00.000+02:00 INFO 97042 --- [ProxyService-16] e.o.shinyproxy.ShinyProxyTestStrategy : [user=jack proxyId=36ab83be-cdf8-4710-bdae-ae7cc5621e24 specId=demo] Waiting: Checking application reachable at http://localhost:40006 (11/30)
Similarly to step 5, this step has a timeout, which can be changed using the
proxy.container-wait-time
property. When the timeout is reached, the app (including the container) is
stopped and the user is informed that the app has failed to start.
In addition, the following configuration properties influences this step:
proxy.container-wait-timeout
proxy.docker.container-protocol
proxy.docker.target-url
proxy.kubernetes.container-protocol
proxy.ecs.container-protocol
target-path
Since version 3.2.0, ShinyProxy also checks the health of the container during both step 5 and 6. This ensures that the step is aborted as soon as the container fails (before the step would be aborted when the timeout was reached).
Up
After an app has successfully started up, its status changes to Up
.
Stopping
Apps can be stopped by the user or automatically by ShinyProxy, after which they
go into the Stopping
state. As long as ShinyProxy is working on removing the
resources of this app, the status stays at Stopping
An app can be stopped for one of the following reasons:
-
the user clicked the
Stop App
button (in the navbar, theSwitch Instance
modal or theMy Apps
section/modal).Example log message:
2025-06-02T16:00:00.000+02:00 INFO 287206 --- [ XNIO-1 task-5] e.o.containerproxy.service.ProxyService : [user=jack proxyId=b9a7a576-beec-41cc-8f3a-8d2104967df9 specId=demo] Proxy being stopped by user jack 2025-06-02T16:00:00.000+02:00 INFO 287206 --- [ XNIO-1 task-5] e.o.containerproxy.service.ProxyService : [user=jack proxyId=b9a7a576-beec-41cc-8f3a-8d2104967df9 specId=demo] Proxy released
-
the user clicked the
Restart app
button -
an admin user clicked the
Stop App
button on the admin page. Example log message (notice that the app is owned byjack
but stopped byjeff
):Example log message:
2025-06-02T16:00:00.000+02:00 INFO 287206 --- [ XNIO-1 task-5] e.o.containerproxy.service.ProxyService : [user=jack proxyId=b9a7a576-beec-41cc-8f3a-8d2104967df9 specId=demo] Proxy being stopped by user jeff 2025-06-02T16:00:00.000+02:00 INFO 287206 --- [ XNIO-1 task-5] e.o.containerproxy.service.ProxyService : [user=jack proxyId=b9a7a576-beec-41cc-8f3a-8d2104967df9 specId=demo] Proxy released
-
the user logged out. This can be disabled using the
proxy.default-stop-proxy-on-logout
option. A user can be logged out for multiple reasons:-
the user clicked the logout button
-
the session of the user expired. The session timeout can be changed using the
server.servlet.session.timeout
option.Note: the session of a user can only expire if the user is inactive. Therefore, if the user has an app open in their browser, they’re not logged out.
-
the OpenID Connect session of the user expired or was invalidated (e.g. because the user logged out from the IDP). This can be disabled using the
proxy.openid.ignore-session-expire
option.
Example log message:
2025-06-02T16:00:00.000+02:00 INFO 287206 --- [ XNIO-1 task-2] e.o.containerproxy.service.UserService : User logged out [user: jeff] 2025-06-02T16:00:00.000+02:00 INFO 287206 --- [ProxyService-16] e.o.containerproxy.service.ProxyService : [user=jeff proxyId=d9b86940-a45f-4cdf-9409-198c9d29bf29 specId=demo] Proxy released
-
-
the ShinyProxy server was shutdown. This can be disabled using the
proxy.stop-proxies-on-shutdown
option -
the app was stopped using the API
-
the app was inactive for 60 seconds. In order for ShinyProxy to know whether an app is active, a heartbeat system is used. If the app uses a WebSocket connection, this connection is re-used for sending the heartbeats. If there is no WebSocket connection, the front-end of ShinyProxy automatically sends heartbeat requests to the back-end. Using this system, ShinyProxy can automatically stop apps that are no longer being used. Note that an app is considered active, as long as the app is opened in a web-browser (since it then automatically sends heartbeats). ShinyProxy doesn’t check whether an user is doing any actions (clicking, typing etc) on the page. In other words, when a user would not be sitting at their computer, the app would still be considered active. The timeout can be increased or removed using the
proxy.heartbeat-timeout
property. The interval at which heartbeats are sent is controlled by theproxy.heartbeat-rate
property.Example log message:
2025-06-02T16:00:00.000+02:00 INFO 302877 --- [GlobalEventLoop] e.o.c.s.heartbeat.ActiveProxiesService : [user=jack proxyId=b4de8d10-7917-4a55-9474-2ec4e91ee681 specId=demo] Releasing inactive proxy [silence: 76222ms] 2025-06-02T16:00:00.000+02:00 INFO 302877 --- [ProxyService-16] e.o.containerproxy.service.ProxyService : [user=jack proxyId=b4de8d10-7917-4a55-9474-2ec4e91ee681 specId=demo] Proxy released
-
the app reached its maximum lifetime. By default, apps can run indefinitely (if not stopped for another reason). Using the
proxy.default-proxy-max-lifetime
option, the total lifetime of an app can be limited. When the app reaches the maximum lifetime it’s automatically stopped by ShinyProxy, even if it’s in use by the user. Example log message:2025-06-02T16:00:00.000+02:00 INFO 301028 --- [GlobalEventLoop] e.o.c.service.ProxyMaxLifetimeService : [user=jack proxyId=2c17e42c-bfe4-4e4f-a521-0c216d19911e specId=demo] Forcefully releasing proxy because it reached the max lifetime [uptime: 9 minutes 45 seconds] 2025-06-02T16:00:00.000+02:00 INFO 301028 --- [ProxyService-16] e.o.containerproxy.service.ProxyService : [user=jack proxyId=2c17e42c-bfe4-4e4f-a521-0c216d19911e specId=demo] Proxy released
-
the app crashed. ShinyProxy doesn’t actively monitor the health of apps. However, if a request to an app fails, ShinyProxy checks whether the container is still running. If the container has failed, the app is marked as crashed and stopped. Example log message:
2025-06-02T16:00:00.000+02:00 WARN 302877 --- [ XNIO-1 I/O-6] e.o.c.b.docker.DockerEngineBackend : [user=jack proxyId=72dd9f80-c76a-4cb4-9007-bed69dcf087a specId=demo] Docker container failed: container not running, state reported by docker: {"Status":"exited","Running":false,"Paused":false,"Restarting":false,"Pid":0,"ExitCode":137,"StartedAt":1749134471293,"FinishedAt":1749134479738,"Error":"","OOMKilled":false,"Health":null} 2025-06-02T16:00:00.000+02:00 INFO 302877 --- [ XNIO-1 I/O-6] e.o.c.util.ProxyMappingManager : [user=jack proxyId=72dd9f80-c76a-4cb4-9007-bed69dcf087a specId=demo] Proxy unreachable/crashed, stopping it now, failed request: GET http://localhost:8080/proxy_endpoint/72dd9f80-c76a-4cb4-9007-bed69dcf087a/?sp_proxy_id=72dd9f80-c76a-4cb4-9007-bed69dcf087a was proxied to: http://localhost:40000/?sp_proxy_id=72dd9f80-c76a-4cb4-9007-bed69dcf087a, status: 503 2025-06-02T16:00:00.000+02:00 INFO 302877 --- [ProxyService-16] e.o.containerproxy.service.ProxyService : [user=jack proxyId=72dd9f80-c76a-4cb4-9007-bed69dcf087a specId=demo] Proxy released
Note: many of these configuration options can be overwritten using an app specific option, see the linked documentation of each property. More examples on running apps in the background are available on GitHub.
Stopped
As soon as ShinyProxy has removed all resources of an app, the status of the app
is changed to Stopped
.