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:

stateDiagram-v2 direction LR classDef stroke stroke-width: 0px; classDef color color: white; classDef New fill:#f0ad4e; class New New stroke color classDef Up fill:#5cb85c; class Up Up stroke color classDef Stop fill:#d9534f; class Stopping Stop stroke color class Stopped Stop stroke color [*] --> New New --> Up Up --> Stopping Stopping --> Stopped Stopped --> [*]

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:

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:

  1. validate and process any parameters
  2. parse and resolve any SpEL expressions
  3. pull the container image, adhering to the configured image-pull-policy on Docker and Kubernetes
  4. create a container for the app using one of the container back-ends
  5. wait for the container to be running
  6. 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:

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, the Switch Instance modal or the My 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 by jack but stopped by jeff):

    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 the proxy.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.