├── .Rbuildignore ├── .devcontainer └── devcontainer.json ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ └── pkgdown.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE.md ├── NAMESPACE ├── R ├── app-detection.R ├── configuration.R ├── container.R ├── docker-core.R ├── export.R ├── image.R ├── sitrep.R └── utils.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── inst ├── demo-py-shiny-containerization.R ├── demo-r-shiny-containerization.R ├── examples │ └── shiny │ │ ├── python │ │ └── hello-docker-plain │ │ │ └── app.py │ │ └── r │ │ ├── hello-docker-ggplot2 │ │ └── app.R │ │ └── hello-docker-plain │ │ └── app.R └── templates │ ├── Dockerfile_Python │ ├── Dockerfile_R │ └── dockerignore ├── man ├── build_image.Rd ├── check_app_directory.Rd ├── check_docker_available.Rd ├── check_dockerfile.Rd ├── check_shiny_app_files.Rd ├── cleanup_container.Rd ├── create_docker_compose.Rd ├── create_dockerfile.Rd ├── create_dockerignore.Rd ├── detect_app_type.Rd ├── detect_dependencies.Rd ├── detect_python_dependencies.Rd ├── detect_r_dependencies.Rd ├── dockerize.Rd ├── execute_docker_command.Rd ├── export.Rd ├── extract_library_packages.Rd ├── extract_namespace_packages.Rd ├── figures │ ├── shinydocker-animated-logo.svg │ └── shinydocker-logo.svg ├── find_available_port.Rd ├── find_containers_for_app.Rd ├── format_dependencies.Rd ├── format_env_vars.Rd ├── generate_app_url.Rd ├── generate_image_tag.Rd ├── get_app_issues.Rd ├── get_docker_issues.Rd ├── get_platform_flag.Rd ├── handle_multiple_containers.Rd ├── handle_port_configuration.Rd ├── has_shiny_app_files.Rd ├── health_dependencies.Rd ├── health_docker_compose.Rd ├── health_docker_config.Rd ├── health_docker_containers.Rd ├── health_docker_daemon.Rd ├── health_docker_image.Rd ├── health_docker_installation.Rd ├── health_docker_permissions.Rd ├── health_docker_resources.Rd ├── health_port_availability.Rd ├── health_shiny_app.Rd ├── health_system_info.Rd ├── is_arm64.Rd ├── is_docker_available.Rd ├── is_docker_compose_available.Rd ├── is_port_available.Rd ├── prepare_build_arguments.Rd ├── process_containers.Rd ├── process_dockerfile_template.Rd ├── remove_shinydocker_images.Rd ├── run_container.Rd ├── run_docker_prune.Rd ├── run_with_compose.Rd ├── run_with_docker.Rd ├── should_use_compose.Rd ├── sitrep_app_conversion.Rd ├── sitrep_docker.Rd ├── stop_all_containers.Rd ├── stop_container.Rd ├── stop_containers_for_app.Rd ├── stop_docker_container.Rd ├── stop_running_containers.Rd ├── stop_with_compose.Rd └── update_compose_port.Rd └── shinydocker.Rproj /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.devcontainer$ 4 | ^\.github$ 5 | ^LICENSE\.md$ 6 | ^README\.Rmd$ 7 | ^_pkgdown\.yml$ 8 | ^docs$ 9 | ^pkgdown$ 10 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // Config options: https://github.com/rocker-org/devcontainer-templates/tree/main/src/r-ver 2 | { 3 | "name": "R Package Dev Prebuilt (rocker/r-ver base)", 4 | // Image to pull when not building from scratch. 5 | // See .github/devcontainer/devcontainer.json for build details 6 | // and .github/workflows/pre-build-devcontainer.yml for how the 7 | // image is built using GitHub Actions/CI 8 | "image": "ghcr.io/coatless-devcontainer/r-pkg:latest" 9 | } 10 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: R-CMD-check.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | R_KEEP_PKG_SOURCE: yes 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::rcmdcheck 28 | needs: check 29 | 30 | - uses: r-lib/actions/check-r-package@v2 31 | with: 32 | upload-snapshots: true 33 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 34 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Build site 40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 41 | shell: Rscript {0} 42 | 43 | - name: Deploy to GitHub pages 🚀 44 | if: github.event_name != 'pull_request' 45 | uses: JamesIves/github-pages-deploy-action@v4.5.0 46 | with: 47 | clean: false 48 | branch: gh-pages 49 | folder: docs 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .DS_Store 6 | docs 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: shinydocker 2 | Title: Containerize Shiny Applications with Docker 3 | Version: 0.1.0 4 | Authors@R: c( 5 | person("James Joseph", "Balamuta", 6 | email = "james.balamuta@gmail.com", 7 | role = c("aut", "cre"), 8 | comment = c(ORCID = "0000-0003-2826-8458") 9 | ) 10 | ) 11 | Description: Provides tools to export Shiny applications (R or Python) into 12 | Docker containers. This package generates appropriate Docker configuration, 13 | builds the container, and provides utilities to run the containerized application. 14 | URL: https://r-pkg.thecoatlessprofessor.com/shinydocker/, https://github.com/coatless-rpkg/shinydocker 15 | BugReports: https://github.com/coatless-rpkg/shinydocker/issues 16 | License: AGPL (>= 3) 17 | SystemRequirements: Docker (>= 28.0.0) 18 | Depends: R (>= 4.4) 19 | Imports: 20 | yaml, 21 | processx, 22 | fs, 23 | glue, 24 | cli 25 | Suggests: 26 | testthat (>= 3.0.0) 27 | Encoding: UTF-8 28 | Roxygen: list(markdown = TRUE) 29 | RoxygenNote: 7.3.2 30 | Config/testthat/edition: 3 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU Affero General Public License 2 | ================================= 3 | 4 | _Version 3, 19 November 2007_ 5 | _Copyright (C) 2007 Free Software Foundation, Inc. <>_ 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this 8 | license document, but changing it is not allowed. 9 | 10 | ## Preamble 11 | 12 | The GNU Affero General Public License is a free, copyleft license for 13 | software and other kinds of works, specifically designed to ensure 14 | cooperation with the community in the case of network server software. 15 | 16 | The licenses for most software and other practical works are designed 17 | to take away your freedom to share and change the works. By contrast, 18 | our General Public Licenses are intended to guarantee your freedom to 19 | share and change all versions of a program--to make sure it remains 20 | free software for all its users. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | Developers that use our General Public Licenses protect your rights 30 | with two steps: (1) assert copyright on the software, and (2) offer 31 | you this License which gives you legal permission to copy, distribute 32 | and/or modify the software. 33 | 34 | A secondary benefit of defending all users' freedom is that 35 | improvements made in alternate versions of the program, if they 36 | receive widespread use, become available for other developers to 37 | incorporate. Many developers of free software are heartened and 38 | encouraged by the resulting cooperation. However, in the case of 39 | software used on network servers, this result may fail to come about. 40 | The GNU General Public License permits making a modified version and 41 | letting the public access it on a server without ever releasing its 42 | source code to the public. 43 | 44 | The GNU Affero General Public License is designed specifically to 45 | ensure that, in such cases, the modified source code becomes available 46 | to the community. It requires the operator of a network server to 47 | provide the source code of the modified version running there to the 48 | users of that server. Therefore, public use of a modified version, on 49 | a publicly accessible server, gives the public access to the source 50 | code of the modified version. 51 | 52 | An older license, called the Affero General Public License and 53 | published by Affero, was designed to accomplish similar goals. This is 54 | a different license, not a version of the Affero GPL, but Affero has 55 | released a new version of the Affero GPL which permits relicensing 56 | under this license. 57 | 58 | The precise terms and conditions for copying, distribution and 59 | modification follow. 60 | 61 | ## TERMS AND CONDITIONS 62 | 63 | ### 0. Definitions. 64 | 65 | "This License" refers to version 3 of the GNU Affero General Public 66 | License. 67 | 68 | "Copyright" also means copyright-like laws that apply to other kinds 69 | of works, such as semiconductor masks. 70 | 71 | "The Program" refers to any copyrightable work licensed under this 72 | License. Each licensee is addressed as "you". "Licensees" and 73 | "recipients" may be individuals or organizations. 74 | 75 | To "modify" a work means to copy from or adapt all or part of the work 76 | in a fashion requiring copyright permission, other than the making of 77 | an exact copy. The resulting work is called a "modified version" of 78 | the earlier work or a work "based on" the earlier work. 79 | 80 | A "covered work" means either the unmodified Program or a work based 81 | on the Program. 82 | 83 | To "propagate" a work means to do anything with it that, without 84 | permission, would make you directly or secondarily liable for 85 | infringement under applicable copyright law, except executing it on a 86 | computer or modifying a private copy. Propagation includes copying, 87 | distribution (with or without modification), making available to the 88 | public, and in some countries other activities as well. 89 | 90 | To "convey" a work means any kind of propagation that enables other 91 | parties to make or receive copies. Mere interaction with a user 92 | through a computer network, with no transfer of a copy, is not 93 | conveying. 94 | 95 | An interactive user interface displays "Appropriate Legal Notices" to 96 | the extent that it includes a convenient and prominently visible 97 | feature that (1) displays an appropriate copyright notice, and (2) 98 | tells the user that there is no warranty for the work (except to the 99 | extent that warranties are provided), that licensees may convey the 100 | work under this License, and how to view a copy of this License. If 101 | the interface presents a list of user commands or options, such as a 102 | menu, a prominent item in the list meets this criterion. 103 | 104 | ### 1. Source Code. 105 | 106 | The "source code" for a work means the preferred form of the work for 107 | making modifications to it. "Object code" means any non-source form of 108 | a work. 109 | 110 | A "Standard Interface" means an interface that either is an official 111 | standard defined by a recognized standards body, or, in the case of 112 | interfaces specified for a particular programming language, one that 113 | is widely used among developers working in that language. 114 | 115 | The "System Libraries" of an executable work include anything, other 116 | than the work as a whole, that (a) is included in the normal form of 117 | packaging a Major Component, but which is not part of that Major 118 | Component, and (b) serves only to enable use of the work with that 119 | Major Component, or to implement a Standard Interface for which an 120 | implementation is available to the public in source code form. A 121 | "Major Component", in this context, means a major essential component 122 | (kernel, window system, and so on) of the specific operating system 123 | (if any) on which the executable work runs, or a compiler used to 124 | produce the work, or an object code interpreter used to run it. 125 | 126 | The "Corresponding Source" for a work in object code form means all 127 | the source code needed to generate, install, and (for an executable 128 | work) run the object code and to modify the work, including scripts to 129 | control those activities. However, it does not include the work's 130 | System Libraries, or general-purpose tools or generally available free 131 | programs which are used unmodified in performing those activities but 132 | which are not part of the work. For example, Corresponding Source 133 | includes interface definition files associated with source files for 134 | the work, and the source code for shared libraries and dynamically 135 | linked subprograms that the work is specifically designed to require, 136 | such as by intimate data communication or control flow between those 137 | subprograms and other parts of the work. 138 | 139 | The Corresponding Source need not include anything that users can 140 | regenerate automatically from other parts of the Corresponding Source. 141 | 142 | The Corresponding Source for a work in source code form is that same 143 | work. 144 | 145 | ### 2. Basic Permissions. 146 | 147 | All rights granted under this License are granted for the term of 148 | copyright on the Program, and are irrevocable provided the stated 149 | conditions are met. This License explicitly affirms your unlimited 150 | permission to run the unmodified Program. The output from running a 151 | covered work is covered by this License only if the output, given its 152 | content, constitutes a covered work. This License acknowledges your 153 | rights of fair use or other equivalent, as provided by copyright law. 154 | 155 | You may make, run and propagate covered works that you do not convey, 156 | without conditions so long as your license otherwise remains in force. 157 | You may convey covered works to others for the sole purpose of having 158 | them make modifications exclusively for you, or provide you with 159 | facilities for running those works, provided that you comply with the 160 | terms of this License in conveying all material for which you do not 161 | control copyright. Those thus making or running the covered works for 162 | you must do so exclusively on your behalf, under your direction and 163 | control, on terms that prohibit them from making any copies of your 164 | copyrighted material outside their relationship with you. 165 | 166 | Conveying under any other circumstances is permitted solely under the 167 | conditions stated below. Sublicensing is not allowed; section 10 makes 168 | it unnecessary. 169 | 170 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 171 | 172 | No covered work shall be deemed part of an effective technological 173 | measure under any applicable law fulfilling obligations under article 174 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 175 | similar laws prohibiting or restricting circumvention of such 176 | measures. 177 | 178 | When you convey a covered work, you waive any legal power to forbid 179 | circumvention of technological measures to the extent such 180 | circumvention is effected by exercising rights under this License with 181 | respect to the covered work, and you disclaim any intention to limit 182 | operation or modification of the work as a means of enforcing, against 183 | the work's users, your or third parties' legal rights to forbid 184 | circumvention of technological measures. 185 | 186 | ### 4. Conveying Verbatim Copies. 187 | 188 | You may convey verbatim copies of the Program's source code as you 189 | receive it, in any medium, provided that you conspicuously and 190 | appropriately publish on each copy an appropriate copyright notice; 191 | keep intact all notices stating that this License and any 192 | non-permissive terms added in accord with section 7 apply to the code; 193 | keep intact all notices of the absence of any warranty; and give all 194 | recipients a copy of this License along with the Program. 195 | 196 | You may charge any price or no price for each copy that you convey, 197 | and you may offer support or warranty protection for a fee. 198 | 199 | ### 5. Conveying Modified Source Versions. 200 | 201 | You may convey a work based on the Program, or the modifications to 202 | produce it from the Program, in the form of source code under the 203 | terms of section 4, provided that you also meet all of these 204 | conditions: 205 | 206 | - a) The work must carry prominent notices stating that you modified 207 | it, and giving a relevant date. 208 | - b) The work must carry prominent notices stating that it is 209 | released under this License and any conditions added under 210 | section 7. This requirement modifies the requirement in section 4 211 | to "keep intact all notices". 212 | - c) You must license the entire work, as a whole, under this 213 | License to anyone who comes into possession of a copy. This 214 | License will therefore apply, along with any applicable section 7 215 | additional terms, to the whole of the work, and all its parts, 216 | regardless of how they are packaged. This License gives no 217 | permission to license the work in any other way, but it does not 218 | invalidate such permission if you have separately received it. 219 | - d) If the work has interactive user interfaces, each must display 220 | Appropriate Legal Notices; however, if the Program has interactive 221 | interfaces that do not display Appropriate Legal Notices, your 222 | work need not make them do so. 223 | 224 | A compilation of a covered work with other separate and independent 225 | works, which are not by their nature extensions of the covered work, 226 | and which are not combined with it such as to form a larger program, 227 | in or on a volume of a storage or distribution medium, is called an 228 | "aggregate" if the compilation and its resulting copyright are not 229 | used to limit the access or legal rights of the compilation's users 230 | beyond what the individual works permit. Inclusion of a covered work 231 | in an aggregate does not cause this License to apply to the other 232 | parts of the aggregate. 233 | 234 | ### 6. Conveying Non-Source Forms. 235 | 236 | You may convey a covered work in object code form under the terms of 237 | sections 4 and 5, provided that you also convey the machine-readable 238 | Corresponding Source under the terms of this License, in one of these 239 | ways: 240 | 241 | - a) Convey the object code in, or embodied in, a physical product 242 | (including a physical distribution medium), accompanied by the 243 | Corresponding Source fixed on a durable physical medium 244 | customarily used for software interchange. 245 | - b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the Corresponding 255 | Source from a network server at no charge. 256 | - c) Convey individual copies of the object code with a copy of the 257 | written offer to provide the Corresponding Source. This 258 | alternative is allowed only occasionally and noncommercially, and 259 | only if you received the object code with such an offer, in accord 260 | with subsection 6b. 261 | - d) Convey the object code by offering access from a designated 262 | place (gratis or for a charge), and offer equivalent access to the 263 | Corresponding Source in the same way through the same place at no 264 | further charge. You need not require recipients to copy the 265 | Corresponding Source along with the object code. If the place to 266 | copy the object code is a network server, the Corresponding Source 267 | may be on a different server (operated by you or a third party) 268 | that supports equivalent copying facilities, provided you maintain 269 | clear directions next to the object code saying where to find the 270 | Corresponding Source. Regardless of what server hosts the 271 | Corresponding Source, you remain obligated to ensure that it is 272 | available for as long as needed to satisfy these requirements. 273 | - e) Convey the object code using peer-to-peer transmission, 274 | provided you inform other peers where the object code and 275 | Corresponding Source of the work are being offered to the general 276 | public at no charge under subsection 6d. 277 | 278 | A separable portion of the object code, whose source code is excluded 279 | from the Corresponding Source as a System Library, need not be 280 | included in conveying the object code work. 281 | 282 | A "User Product" is either (1) a "consumer product", which means any 283 | tangible personal property which is normally used for personal, 284 | family, or household purposes, or (2) anything designed or sold for 285 | incorporation into a dwelling. In determining whether a product is a 286 | consumer product, doubtful cases shall be resolved in favor of 287 | coverage. For a particular product received by a particular user, 288 | "normally used" refers to a typical or common use of that class of 289 | product, regardless of the status of the particular user or of the way 290 | in which the particular user actually uses, or expects or is expected 291 | to use, the product. A product is a consumer product regardless of 292 | whether the product has substantial commercial, industrial or 293 | non-consumer uses, unless such uses represent the only significant 294 | mode of use of the product. 295 | 296 | "Installation Information" for a User Product means any methods, 297 | procedures, authorization keys, or other information required to 298 | install and execute modified versions of a covered work in that User 299 | Product from a modified version of its Corresponding Source. The 300 | information must suffice to ensure that the continued functioning of 301 | the modified object code is in no case prevented or interfered with 302 | solely because modification has been made. 303 | 304 | If you convey an object code work under this section in, or with, or 305 | specifically for use in, a User Product, and the conveying occurs as 306 | part of a transaction in which the right of possession and use of the 307 | User Product is transferred to the recipient in perpetuity or for a 308 | fixed term (regardless of how the transaction is characterized), the 309 | Corresponding Source conveyed under this section must be accompanied 310 | by the Installation Information. But this requirement does not apply 311 | if neither you nor any third party retains the ability to install 312 | modified object code on the User Product (for example, the work has 313 | been installed in ROM). 314 | 315 | The requirement to provide Installation Information does not include a 316 | requirement to continue to provide support service, warranty, or 317 | updates for a work that has been modified or installed by the 318 | recipient, or for the User Product in which it has been modified or 319 | installed. Access to a network may be denied when the modification 320 | itself materially and adversely affects the operation of the network 321 | or violates the rules and protocols for communication across the 322 | network. 323 | 324 | Corresponding Source conveyed, and Installation Information provided, 325 | in accord with this section must be in a format that is publicly 326 | documented (and with an implementation available to the public in 327 | source code form), and must require no special password or key for 328 | unpacking, reading or copying. 329 | 330 | ### 7. Additional Terms. 331 | 332 | "Additional permissions" are terms that supplement the terms of this 333 | License by making exceptions from one or more of its conditions. 334 | Additional permissions that are applicable to the entire Program shall 335 | be treated as though they were included in this License, to the extent 336 | that they are valid under applicable law. If additional permissions 337 | apply only to part of the Program, that part may be used separately 338 | under those permissions, but the entire Program remains governed by 339 | this License without regard to the additional permissions. 340 | 341 | When you convey a copy of a covered work, you may at your option 342 | remove any additional permissions from that copy, or from any part of 343 | it. (Additional permissions may be written to require their own 344 | removal in certain cases when you modify the work.) You may place 345 | additional permissions on material, added by you to a covered work, 346 | for which you have or can give appropriate copyright permission. 347 | 348 | Notwithstanding any other provision of this License, for material you 349 | add to a covered work, you may (if authorized by the copyright holders 350 | of that material) supplement the terms of this License with terms: 351 | 352 | - a) Disclaiming warranty or limiting liability differently from the 353 | terms of sections 15 and 16 of this License; or 354 | - b) Requiring preservation of specified reasonable legal notices or 355 | author attributions in that material or in the Appropriate Legal 356 | Notices displayed by works containing it; or 357 | - c) Prohibiting misrepresentation of the origin of that material, 358 | or requiring that modified versions of such material be marked in 359 | reasonable ways as different from the original version; or 360 | - d) Limiting the use for publicity purposes of names of licensors 361 | or authors of the material; or 362 | - e) Declining to grant rights under trademark law for use of some 363 | trade names, trademarks, or service marks; or 364 | - f) Requiring indemnification of licensors and authors of that 365 | material by anyone who conveys the material (or modified versions 366 | of it) with contractual assumptions of liability to the recipient, 367 | for any liability that these contractual assumptions directly 368 | impose on those licensors and authors. 369 | 370 | All other non-permissive additional terms are considered "further 371 | restrictions" within the meaning of section 10. If the Program as you 372 | received it, or any part of it, contains a notice stating that it is 373 | governed by this License along with a term that is a further 374 | restriction, you may remove that term. If a license document contains 375 | a further restriction but permits relicensing or conveying under this 376 | License, you may add to a covered work material governed by the terms 377 | of that license document, provided that the further restriction does 378 | not survive such relicensing or conveying. 379 | 380 | If you add terms to a covered work in accord with this section, you 381 | must place, in the relevant source files, a statement of the 382 | additional terms that apply to those files, or a notice indicating 383 | where to find the applicable terms. 384 | 385 | Additional terms, permissive or non-permissive, may be stated in the 386 | form of a separately written license, or stated as exceptions; the 387 | above requirements apply either way. 388 | 389 | ### 8. Termination. 390 | 391 | You may not propagate or modify a covered work except as expressly 392 | provided under this License. Any attempt otherwise to propagate or 393 | modify it is void, and will automatically terminate your rights under 394 | this License (including any patent licenses granted under the third 395 | paragraph of section 11). 396 | 397 | However, if you cease all violation of this License, then your license 398 | from a particular copyright holder is reinstated (a) provisionally, 399 | unless and until the copyright holder explicitly and finally 400 | terminates your license, and (b) permanently, if the copyright holder 401 | fails to notify you of the violation by some reasonable means prior to 402 | 60 days after the cessation. 403 | 404 | Moreover, your license from a particular copyright holder is 405 | reinstated permanently if the copyright holder notifies you of the 406 | violation by some reasonable means, this is the first time you have 407 | received notice of violation of this License (for any work) from that 408 | copyright holder, and you cure the violation prior to 30 days after 409 | your receipt of the notice. 410 | 411 | Termination of your rights under this section does not terminate the 412 | licenses of parties who have received copies or rights from you under 413 | this License. If your rights have been terminated and not permanently 414 | reinstated, you do not qualify to receive new licenses for the same 415 | material under section 10. 416 | 417 | ### 9. Acceptance Not Required for Having Copies. 418 | 419 | You are not required to accept this License in order to receive or run 420 | a copy of the Program. Ancillary propagation of a covered work 421 | occurring solely as a consequence of using peer-to-peer transmission 422 | to receive a copy likewise does not require acceptance. However, 423 | nothing other than this License grants you permission to propagate or 424 | modify any covered work. These actions infringe copyright if you do 425 | not accept this License. Therefore, by modifying or propagating a 426 | covered work, you indicate your acceptance of this License to do so. 427 | 428 | ### 10. Automatic Licensing of Downstream Recipients. 429 | 430 | Each time you convey a covered work, the recipient automatically 431 | receives a license from the original licensors, to run, modify and 432 | propagate that work, subject to this License. You are not responsible 433 | for enforcing compliance by third parties with this License. 434 | 435 | An "entity transaction" is a transaction transferring control of an 436 | organization, or substantially all assets of one, or subdividing an 437 | organization, or merging organizations. If propagation of a covered 438 | work results from an entity transaction, each party to that 439 | transaction who receives a copy of the work also receives whatever 440 | licenses to the work the party's predecessor in interest had or could 441 | give under the previous paragraph, plus a right to possession of the 442 | Corresponding Source of the work from the predecessor in interest, if 443 | the predecessor has it or can get it with reasonable efforts. 444 | 445 | You may not impose any further restrictions on the exercise of the 446 | rights granted or affirmed under this License. For example, you may 447 | not impose a license fee, royalty, or other charge for exercise of 448 | rights granted under this License, and you may not initiate litigation 449 | (including a cross-claim or counterclaim in a lawsuit) alleging that 450 | any patent claim is infringed by making, using, selling, offering for 451 | sale, or importing the Program or any portion of it. 452 | 453 | ### 11. Patents. 454 | 455 | A "contributor" is a copyright holder who authorizes use under this 456 | License of the Program or a work on which the Program is based. The 457 | work thus licensed is called the contributor's "contributor version". 458 | 459 | A contributor's "essential patent claims" are all patent claims owned 460 | or controlled by the contributor, whether already acquired or 461 | hereafter acquired, that would be infringed by some manner, permitted 462 | by this License, of making, using, or selling its contributor version, 463 | but do not include claims that would be infringed only as a 464 | consequence of further modification of the contributor version. For 465 | purposes of this definition, "control" includes the right to grant 466 | patent sublicenses in a manner consistent with the requirements of 467 | this License. 468 | 469 | Each contributor grants you a non-exclusive, worldwide, royalty-free 470 | patent license under the contributor's essential patent claims, to 471 | make, use, sell, offer for sale, import and otherwise run, modify and 472 | propagate the contents of its contributor version. 473 | 474 | In the following three paragraphs, a "patent license" is any express 475 | agreement or commitment, however denominated, not to enforce a patent 476 | (such as an express permission to practice a patent or covenant not to 477 | sue for patent infringement). To "grant" such a patent license to a 478 | party means to make such an agreement or commitment not to enforce a 479 | patent against the party. 480 | 481 | If you convey a covered work, knowingly relying on a patent license, 482 | and the Corresponding Source of the work is not available for anyone 483 | to copy, free of charge and under the terms of this License, through a 484 | publicly available network server or other readily accessible means, 485 | then you must either (1) cause the Corresponding Source to be so 486 | available, or (2) arrange to deprive yourself of the benefit of the 487 | patent license for this particular work, or (3) arrange, in a manner 488 | consistent with the requirements of this License, to extend the patent 489 | license to downstream recipients. "Knowingly relying" means you have 490 | actual knowledge that, but for the patent license, your conveying the 491 | covered work in a country, or your recipient's use of the covered work 492 | in a country, would infringe one or more identifiable patents in that 493 | country that you have reason to believe are valid. 494 | 495 | If, pursuant to or in connection with a single transaction or 496 | arrangement, you convey, or propagate by procuring conveyance of, a 497 | covered work, and grant a patent license to some of the parties 498 | receiving the covered work authorizing them to use, propagate, modify 499 | or convey a specific copy of the covered work, then the patent license 500 | you grant is automatically extended to all recipients of the covered 501 | work and works based on it. 502 | 503 | A patent license is "discriminatory" if it does not include within the 504 | scope of its coverage, prohibits the exercise of, or is conditioned on 505 | the non-exercise of one or more of the rights that are specifically 506 | granted under this License. You may not convey a covered work if you 507 | are a party to an arrangement with a third party that is in the 508 | business of distributing software, under which you make payment to the 509 | third party based on the extent of your activity of conveying the 510 | work, and under which the third party grants, to any of the parties 511 | who would receive the covered work from you, a discriminatory patent 512 | license (a) in connection with copies of the covered work conveyed by 513 | you (or copies made from those copies), or (b) primarily for and in 514 | connection with specific products or compilations that contain the 515 | covered work, unless you entered into that arrangement, or that patent 516 | license was granted, prior to 28 March 2007. 517 | 518 | Nothing in this License shall be construed as excluding or limiting 519 | any implied license or other defenses to infringement that may 520 | otherwise be available to you under applicable patent law. 521 | 522 | ### 12. No Surrender of Others' Freedom. 523 | 524 | If conditions are imposed on you (whether by court order, agreement or 525 | otherwise) that contradict the conditions of this License, they do not 526 | excuse you from the conditions of this License. If you cannot convey a 527 | covered work so as to satisfy simultaneously your obligations under 528 | this License and any other pertinent obligations, then as a 529 | consequence you may not convey it at all. For example, if you agree to 530 | terms that obligate you to collect a royalty for further conveying 531 | from those to whom you convey the Program, the only way you could 532 | satisfy both those terms and this License would be to refrain entirely 533 | from conveying the Program. 534 | 535 | ### 13. Remote Network Interaction; Use with the GNU General Public License. 536 | 537 | Notwithstanding any other provision of this License, if you modify the 538 | Program, your modified version must prominently offer all users 539 | interacting with it remotely through a computer network (if your 540 | version supports such interaction) an opportunity to receive the 541 | Corresponding Source of your version by providing access to the 542 | Corresponding Source from a network server at no charge, through some 543 | standard or customary means of facilitating copying of software. This 544 | Corresponding Source shall include the Corresponding Source for any 545 | work covered by version 3 of the GNU General Public License that is 546 | incorporated pursuant to the following paragraph. 547 | 548 | Notwithstanding any other provision of this License, you have 549 | permission to link or combine any covered work with a work licensed 550 | under version 3 of the GNU General Public License into a single 551 | combined work, and to convey the resulting work. The terms of this 552 | License will continue to apply to the part which is the covered work, 553 | but the work with which it is combined will remain governed by version 554 | 3 of the GNU General Public License. 555 | 556 | ### 14. Revised Versions of this License. 557 | 558 | The Free Software Foundation may publish revised and/or new versions 559 | of the GNU Affero General Public License from time to time. Such new 560 | versions will be similar in spirit to the present version, but may 561 | differ in detail to address new problems or concerns. 562 | 563 | Each version is given a distinguishing version number. If the Program 564 | specifies that a certain numbered version of the GNU Affero General 565 | Public License "or any later version" applies to it, you have the 566 | option of following the terms and conditions either of that numbered 567 | version or of any later version published by the Free Software 568 | Foundation. If the Program does not specify a version number of the 569 | GNU Affero General Public License, you may choose any version ever 570 | published by the Free Software Foundation. 571 | 572 | If the Program specifies that a proxy can decide which future versions 573 | of the GNU Affero General Public License can be used, that proxy's 574 | public statement of acceptance of a version permanently authorizes you 575 | to choose that version for the Program. 576 | 577 | Later license versions may give you additional or different 578 | permissions. However, no additional obligations are imposed on any 579 | author or copyright holder as a result of your choosing to follow a 580 | later version. 581 | 582 | ### 15. Disclaimer of Warranty. 583 | 584 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 585 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 586 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT 587 | WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT 588 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 589 | A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND 590 | PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 591 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR 592 | CORRECTION. 593 | 594 | ### 16. Limitation of Liability. 595 | 596 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 597 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR 598 | CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 599 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES 600 | ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT 601 | NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR 602 | LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM 603 | TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER 604 | PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 605 | 606 | ### 17. Interpretation of Sections 15 and 16. 607 | 608 | If the disclaimer of warranty and limitation of liability provided 609 | above cannot be given local legal effect according to their terms, 610 | reviewing courts shall apply local law that most closely approximates 611 | an absolute waiver of all civil liability in connection with the 612 | Program, unless a warranty or assumption of liability accompanies a 613 | copy of the Program in return for a fee. 614 | 615 | END OF TERMS AND CONDITIONS 616 | 617 | ## How to Apply These Terms to Your New Programs 618 | 619 | If you develop a new program, and you want it to be of the greatest 620 | possible use to the public, the best way to achieve this is to make it 621 | free software which everyone can redistribute and change under these 622 | terms. 623 | 624 | To do so, attach the following notices to the program. It is safest to 625 | attach them to the start of each source file to most effectively state 626 | the exclusion of warranty; and each file should have at least the 627 | "copyright" line and a pointer to where the full notice is found. 628 | 629 | 630 | Copyright (C) 631 | 632 | This program is free software: you can redistribute it and/or modify 633 | it under the terms of the GNU Affero General Public License as 634 | published by the Free Software Foundation, either version 3 of the 635 | License, or (at your option) any later version. 636 | 637 | This program is distributed in the hope that it will be useful, 638 | but WITHOUT ANY WARRANTY; without even the implied warranty of 639 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 640 | GNU Affero General Public License for more details. 641 | 642 | You should have received a copy of the GNU Affero General Public License 643 | along with this program. If not, see . 644 | 645 | Also add information on how to contact you by electronic and paper 646 | mail. 647 | 648 | If your software can interact with users remotely through a computer 649 | network, you should also make sure that it provides a way for users to 650 | get its source. For example, if your program is a web application, its 651 | interface could display a "Source" link that leads users to an archive 652 | of the code. There are many ways you could offer source, and different 653 | solutions will be better for different programs; see section 13 for 654 | the specific requirements. 655 | 656 | You should also get your employer (if you work as a programmer) or 657 | school, if any, to sign a "copyright disclaimer" for the program, if 658 | necessary. For more information on this, and how to apply and follow 659 | the GNU AGPL, see . 660 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(build_image) 4 | export(cleanup_container) 5 | export(dockerize) 6 | export(export) 7 | export(run_container) 8 | export(sitrep_app_conversion) 9 | export(sitrep_docker) 10 | export(stop_container) 11 | -------------------------------------------------------------------------------- /R/app-detection.R: -------------------------------------------------------------------------------- 1 | #' Detect the type of Shiny application (R or Python) 2 | #' 3 | #' @param app_dir Character. Path to the Shiny application directory. 4 | #' 5 | #' @return Character. Either "r" or "python". 6 | #' 7 | #' @keywords internal 8 | detect_app_type <- function(app_dir) { 9 | # Look for R files 10 | r_files <- list.files(app_dir, pattern = "\\.R$|\\.r$", recursive = TRUE) 11 | 12 | # Look for Python files 13 | py_files <- list.files(app_dir, pattern = "\\.py$", recursive = TRUE) 14 | 15 | # Check for Python's app.py or server.py 16 | has_py_app <- any(grepl("app\\.py$|server\\.py$", py_files)) 17 | 18 | # Check for R's app.R, server.R, or ui.R 19 | has_r_app <- any(grepl("app\\.R$|server\\.R$|ui\\.R$", r_files, ignore.case = TRUE)) 20 | 21 | if (has_py_app && !has_r_app) { 22 | return("python") 23 | } else if (has_r_app || length(r_files) > 0) { 24 | return("r") 25 | } else if (length(py_files) > 0) { 26 | return("python") 27 | } else { 28 | # If we can't determine, default to R 29 | warning("Could not determine app type, defaulting to R. Specify app_type manually if needed.") 30 | return("r") 31 | } 32 | } 33 | 34 | #' Check if directory contains Shiny app files 35 | #' 36 | #' @param app_dir Character. Path to the Shiny application directory. 37 | #' 38 | #' @return Logical. TRUE if the directory contains valid Shiny app files. 39 | #' 40 | #' @keywords internal 41 | has_shiny_app_files <- function(app_dir) { 42 | # Check for R Shiny app files 43 | has_r_app <- file.exists(file.path(app_dir, "app.R")) || 44 | (file.exists(file.path(app_dir, "ui.R")) && 45 | file.exists(file.path(app_dir, "server.R"))) 46 | 47 | # Check for Python Shiny app files 48 | has_py_app <- file.exists(file.path(app_dir, "app.py")) || 49 | (file.exists(file.path(app_dir, "ui.py")) && 50 | file.exists(file.path(app_dir, "server.py"))) 51 | 52 | return(has_r_app || has_py_app) 53 | } 54 | 55 | #' Check if directory contains valid Shiny app files and abort if not 56 | #' 57 | #' @param app_dir Character. Path to the Shiny application directory. 58 | #' 59 | #' @return Invisibly returns TRUE if valid, otherwise aborts with an error message. 60 | #' 61 | #' @keywords internal 62 | check_shiny_app_files <- function(app_dir) { 63 | if (!has_shiny_app_files(app_dir)) { 64 | cli::cli_abort(c( 65 | "Directory does not appear to contain a valid Shiny application.", 66 | "i" = "Expected files for R apps: {.file app.R}, or {.file ui.R} and {.file server.R}", 67 | "i" = "Expected files for Python apps: {.file app.py}, or {.file ui.py} and {.file server.py}" 68 | )) 69 | } 70 | 71 | invisible(TRUE) 72 | } 73 | 74 | #' Detect dependencies for a Shiny application 75 | #' 76 | #' @param app_dir Character. Path to the Shiny application directory. 77 | #' @param app_type Character. Either "r" or "python". 78 | #' 79 | #' @return Character vector with detected dependencies. 80 | #' 81 | #' @keywords internal 82 | detect_dependencies <- function(app_dir, app_type) { 83 | if (app_type == "r") { 84 | return(detect_r_dependencies(app_dir)) 85 | } else if (app_type == "python") { 86 | return(detect_python_dependencies(app_dir)) 87 | } 88 | } 89 | #' Extract package names from library() and require() calls 90 | #' 91 | #' @param r_code Character vector. R code to analyze. 92 | #' 93 | #' @return Character vector of package names. 94 | #' 95 | #' @keywords internal 96 | extract_library_packages <- function(r_code) { 97 | lib_pattern <- "library\\(([^,)]+)\\)|require\\(([^,)]+)\\)" 98 | lib_matches <- gregexpr(lib_pattern, r_code, perl = TRUE) 99 | 100 | packages <- character(0) 101 | for (i in seq_along(r_code)) { 102 | if (lib_matches[[i]][1] != -1) { 103 | matches <- regmatches(r_code[i], lib_matches[[i]]) 104 | for (match in matches) { 105 | # Extract the package name from library()/require() call 106 | pkg_name <- gsub("library\\(([^,)]+)\\)|require\\(([^,)]+)\\)", "\\1\\2", match, perl = TRUE) 107 | # Remove quotes if present 108 | pkg_name <- gsub("^['\"]|['\"]$", "", pkg_name) 109 | packages <- c(packages, pkg_name) 110 | } 111 | } 112 | } 113 | 114 | return(packages) 115 | } 116 | 117 | #' Extract package names from namespace calls (package::function) 118 | #' 119 | #' @param r_code Character vector. R code to analyze. 120 | #' 121 | #' @return Character vector of package names. 122 | #' 123 | #' @keywords internal 124 | extract_namespace_packages <- function(r_code) { 125 | pkg_pattern <- "([[:alnum:].]+)::" 126 | pkg_matches <- gregexpr(pkg_pattern, r_code, perl = TRUE) 127 | 128 | packages <- character(0) 129 | for (i in seq_along(r_code)) { 130 | if (pkg_matches[[i]][1] != -1) { 131 | matches <- regmatches(r_code[i], pkg_matches[[i]]) 132 | for (match in matches) { 133 | # Extract the package name from package:: call 134 | pkg_name <- gsub("([[:alnum:].]+)::", "\\1", match, perl = TRUE) 135 | packages <- c(packages, pkg_name) 136 | } 137 | } 138 | } 139 | 140 | return(packages) 141 | } 142 | 143 | #' Detect R package dependencies 144 | #' 145 | #' @param app_dir Character. Path to the Shiny application directory. 146 | #' 147 | #' @return Character vector of detected R package dependencies. 148 | #' 149 | #' @keywords internal 150 | detect_r_dependencies <- function(app_dir) { 151 | # Look for R files 152 | r_files <- list.files(app_dir, pattern = "\\.R$|\\.r$", full.names = TRUE, recursive = TRUE) 153 | 154 | if (length(r_files) == 0) { 155 | warning("No R files found in the application directory.") 156 | return(c("shiny")) 157 | } 158 | 159 | # Read all R files 160 | r_code <- unlist(lapply(r_files, readLines)) 161 | 162 | # Extract packages from different patterns 163 | library_packages <- extract_library_packages(r_code) 164 | namespace_packages <- extract_namespace_packages(r_code) 165 | 166 | # Combine and ensure shiny is included 167 | packages <- unique(c("shiny", library_packages, namespace_packages)) 168 | return(sort(packages)) 169 | } 170 | 171 | #' Detect Python package dependencies 172 | #' 173 | #' @param app_dir Character. Path to the Shiny application directory. 174 | #' 175 | #' @return Character vector of detected Python package dependencies. 176 | #' 177 | #' @keywords internal 178 | detect_python_dependencies <- function(app_dir) { 179 | # Look for requirements.txt 180 | req_file <- file.path(app_dir, "requirements.txt") 181 | 182 | if (file.exists(req_file)) { 183 | # Read requirements.txt 184 | packages <- readLines(req_file) 185 | # Remove comments and empty lines 186 | packages <- packages[!grepl("^\\s*#", packages) & nzchar(trimws(packages))] 187 | # Remove version specifications 188 | packages <- gsub("([^<>=~!]+)[<>=~!].*", "\\1", packages) 189 | # Trim whitespace 190 | packages <- trimws(packages) 191 | return(packages) 192 | } 193 | 194 | # If no requirements.txt, look for import statements in Python files 195 | py_files <- list.files(app_dir, pattern = "\\.py$", full.names = TRUE, recursive = TRUE) 196 | 197 | if (length(py_files) == 0) { 198 | warning("No Python files found in the application directory.") 199 | return(c("shiny")) 200 | } 201 | 202 | # Read all Python files 203 | py_code <- unlist(lapply(py_files, readLines)) 204 | 205 | # Look for import statements 206 | import_pattern <- "^\\s*import\\s+([^\\s.]+)|^\\s*from\\s+([^\\s.]+)" 207 | import_matches <- gregexpr(import_pattern, py_code, perl = TRUE) 208 | 209 | # Extract package names 210 | packages <- character(0) 211 | for (i in seq_along(py_code)) { 212 | if (import_matches[[i]][1] != -1) { 213 | matches <- regmatches(py_code[i], import_matches[[i]]) 214 | for (match in matches) { 215 | # Extract the package name from import or from statement 216 | pkg_name <- gsub("^\\s*import\\s+([^\\s.]+)|^\\s*from\\s+([^\\s.]+)", "\\1\\2", match, perl = TRUE) 217 | packages <- c(packages, pkg_name) 218 | } 219 | } 220 | } 221 | 222 | # Remove duplicates and sort 223 | packages <- sort(unique(c("shiny", packages))) 224 | return(packages) 225 | } -------------------------------------------------------------------------------- /R/configuration.R: -------------------------------------------------------------------------------- 1 | #' Create a Dockerfile for a Shiny application 2 | #' 3 | #' @param output_dir Character. Directory where the Dockerfile should be created. 4 | #' @param app_type Character. Either "r" or "python". 5 | #' @param custom_template Character. Path to a custom Dockerfile template. 6 | #' @param port Integer. The port to expose. 7 | #' @param dependencies List. Application dependencies. 8 | #' @param env_vars Named character vector. Environment variables. 9 | #' @param ... Additional arguments. 10 | #' 11 | #' @return Character. Path to the created Dockerfile. 12 | #' 13 | #' @keywords internal 14 | create_dockerfile <- function(output_dir, app_type, custom_template = NULL, 15 | port = 3838, dependencies = list(), env_vars = NULL, ...) { 16 | # Determine which template to use 17 | if (!is.null(custom_template) && file.exists(custom_template)) { 18 | template_content <- readLines(custom_template) 19 | cli::cli_alert_info("Using custom Dockerfile template: {.file {custom_template}}") 20 | } else { 21 | # Use built-in template 22 | template_name <- paste0("Dockerfile_", ifelse(app_type == "r", "R", "Python")) 23 | template_path <- system.file("templates", template_name, package = "shinydocker") 24 | 25 | if (!file.exists(template_path) || nchar(template_path) == 0) { 26 | cli::cli_abort(c( 27 | "Could not find template file: {.file {template_name}}", 28 | "i" = "Please ensure the package is installed correctly." 29 | )) 30 | } 31 | 32 | cli::cli_alert_info("Using built-in {ifelse(app_type == 'r', 'R', 'Python')} Dockerfile template") 33 | template_content <- readLines(template_path) 34 | } 35 | 36 | # Process template 37 | content <- process_dockerfile_template(template_content, app_type, port, dependencies, env_vars) 38 | 39 | # Write Dockerfile 40 | dockerfile_path <- file.path(output_dir, "Dockerfile") 41 | writeLines(content, dockerfile_path) 42 | 43 | return(dockerfile_path) 44 | } 45 | 46 | #' Process a Dockerfile template by replacing placeholders 47 | #' 48 | #' @param template_content Character vector. Template content. 49 | #' @param app_type Character. Either "r" or "python". 50 | #' @param port Integer. The port to expose. 51 | #' @param dependencies List. Application dependencies. 52 | #' @param env_vars Named character vector. Environment variables. 53 | #' 54 | #' @return Character vector with processed template. 55 | #' 56 | #' @keywords internal 57 | process_dockerfile_template <- function(template_content, app_type, port, dependencies, env_vars) { 58 | content <- template_content 59 | 60 | # Replace port placeholder 61 | content <- gsub("\\{\\{PORT\\}\\}", as.character(port), content) 62 | 63 | # Replace dependencies placeholder 64 | content <- gsub("\\{\\{DEPENDENCIES\\}\\}", format_dependencies(app_type, dependencies), content) 65 | 66 | # Replace env vars placeholder 67 | content <- gsub("\\{\\{ENV_VARS\\}\\}", format_env_vars(env_vars), content) 68 | 69 | return(content) 70 | } 71 | 72 | #' Format dependencies for inclusion in Dockerfile 73 | #' 74 | #' @param app_type Character. Either "r" or "python". 75 | #' @param dependencies List of dependencies. 76 | #' 77 | #' @return Character string with formatted dependencies. 78 | #' 79 | #' @keywords internal 80 | format_dependencies <- function(app_type, dependencies) { 81 | if (length(dependencies) == 0) { 82 | return(paste0("# No additional ", ifelse(app_type == "r", "R", "Python"), " packages to install")) 83 | } 84 | 85 | if (app_type == "r") { 86 | return(paste0('RUN install2.r --error ', paste(dependencies, collapse = " "))) 87 | } else { 88 | return(paste0('RUN pip install ', paste(dependencies, collapse = " "))) 89 | } 90 | } 91 | 92 | #' Format environment variables for inclusion in Dockerfile 93 | #' 94 | #' @param env_vars Named character vector. Environment variables. 95 | #' 96 | #' @return Character string with formatted environment variables. 97 | #' 98 | #' @keywords internal 99 | format_env_vars <- function(env_vars) { 100 | if (is.null(env_vars) || length(env_vars) == 0) { 101 | return("# No environment variables set") 102 | } 103 | 104 | env_str <- "" 105 | for (env_name in names(env_vars)) { 106 | env_str <- paste0(env_str, "ENV ", env_name, "=", env_vars[env_name], "\n") 107 | } 108 | 109 | return(trimws(env_str)) 110 | } 111 | 112 | #' Create a docker-compose.yml file for a Shiny application 113 | #' 114 | #' @param output_dir Character. Directory where the file should be created. 115 | #' @param port Integer. The port to expose. 116 | #' @param env_vars Named character vector. Environment variables. 117 | #' @param ... Additional arguments. 118 | #' 119 | #' @return Character. Path to the created file. 120 | #' 121 | #' @keywords internal 122 | create_docker_compose <- function(output_dir, port = 3838, env_vars = NULL, ...) { 123 | # Create docker-compose.yml content 124 | service_name <- tolower(basename(output_dir)) 125 | service_name <- gsub("[^a-z0-9]", "", service_name) # Clean name for service 126 | 127 | # Basic compose structure 128 | compose_data <- list( 129 | version = "3", 130 | services = list() 131 | ) 132 | 133 | # Format port as a list item (required by docker-compose) 134 | port_mapping <- paste0(port, ":3838") 135 | 136 | # Create service configuration 137 | service_config <- list( 138 | build = ".", 139 | ports = list(port_mapping), # Use a list for ports 140 | restart = "unless-stopped" 141 | ) 142 | 143 | # Add platform configuration if needed (for ARM64 systems) 144 | app_type <- detect_app_type(output_dir) 145 | if (is_arm64() && app_type == "r") { 146 | service_config$platform <- "linux/amd64" 147 | } 148 | 149 | # Add environment variables if provided 150 | if (!is.null(env_vars) && length(env_vars) > 0) { 151 | env_list <- as.list(env_vars) 152 | service_config$environment <- env_list 153 | } 154 | 155 | # Add service to compose data 156 | compose_data$services[[service_name]] <- service_config 157 | 158 | # Convert to YAML 159 | compose_content <- yaml::as.yaml(compose_data) 160 | 161 | # Write docker-compose.yml 162 | compose_path <- file.path(output_dir, "docker-compose.yml") 163 | writeLines(compose_content, compose_path) 164 | 165 | return(compose_path) 166 | } 167 | 168 | #' Create a .dockerignore file 169 | #' 170 | #' @param output_dir Character. Directory where the file should be created. 171 | #' 172 | #' @return Character. Path to the created file. 173 | #' 174 | #' @keywords internal 175 | create_dockerignore <- function(output_dir) { 176 | # Get the .dockerignore template 177 | template_path <- system.file("templates", "dockerignore", package = "shinydocker") 178 | 179 | if (!file.exists(template_path) || nchar(template_path) == 0) { 180 | cli::cli_abort(c( 181 | "Could not find .dockerignore template file", 182 | "i" = "Please ensure the package is installed correctly." 183 | )) 184 | } 185 | 186 | # Read template content 187 | ignore_content <- readLines(template_path) 188 | 189 | # Write .dockerignore 190 | ignore_path <- file.path(output_dir, ".dockerignore") 191 | writeLines(ignore_content, ignore_path) 192 | 193 | return(ignore_path) 194 | } -------------------------------------------------------------------------------- /R/container.R: -------------------------------------------------------------------------------- 1 | #' Determine if docker-compose should be used for container operations 2 | #' 3 | #' @param app_dir Character. Path to the Shiny application directory. 4 | #' @param docker_compose Logical. User preference for using docker-compose. 5 | #' 6 | #' @return Logical. TRUE if docker-compose should be used. 7 | #' 8 | #' @keywords internal 9 | should_use_compose <- function(app_dir, docker_compose = TRUE) { 10 | compose_path <- fs::path(app_dir, "docker-compose.yml") 11 | return(docker_compose && is_docker_compose_available() && fs::file_exists(compose_path)) 12 | } 13 | 14 | #' Run container using docker-compose 15 | #' 16 | #' @param app_dir Character. Path to the Shiny application directory. 17 | #' @param port Integer. The port to map. 18 | #' @param env_vars Named character vector. Environment variables. 19 | #' @param detach Logical. Whether to run in detached mode. 20 | #' @param quiet Logical. Whether to suppress output. 21 | #' @param ... Additional arguments passed to processx. 22 | #' 23 | #' @return Character. Container ID if successful. 24 | #' 25 | #' @keywords internal 26 | run_with_compose <- function(app_dir, port, env_vars = NULL, detach = FALSE, quiet = FALSE, ...) { 27 | # Set environment variables for docker-compose 28 | if (!is.null(env_vars) && length(env_vars) > 0) { 29 | cli::cli_alert_info("Setting environment variables...") 30 | for (env_name in names(env_vars)) { 31 | Sys.setenv(env_name = env_vars[env_name]) 32 | } 33 | } 34 | 35 | # Prepare docker-compose command arguments 36 | compose_args <- if (detach) c("up", "-d") else c("up") 37 | 38 | # Execute command 39 | result <- execute_docker_command( 40 | "docker-compose", 41 | c("-f", fs::path(app_dir, "docker-compose.yml"), compose_args), 42 | quiet = quiet, 43 | echo = !quiet, 44 | error_on_status = FALSE, 45 | ... 46 | ) 47 | 48 | # For detached mode, try to get container ID 49 | container_id <- NULL 50 | if (detach && result$status == 0) { 51 | # Use docker-compose ps to get container ID 52 | ps_result <- tryCatch({ 53 | execute_docker_command( 54 | "docker-compose", 55 | c("-f", fs::path(app_dir, "docker-compose.yml"), "ps", "-q"), 56 | quiet = TRUE 57 | ) 58 | }, error = function(e) { 59 | return(NULL) 60 | }) 61 | 62 | if (!is.null(ps_result) && nzchar(ps_result$stdout)) { 63 | container_id <- trimws(ps_result$stdout) 64 | } 65 | } 66 | 67 | return(container_id) 68 | } 69 | 70 | #' Run container using docker run 71 | #' 72 | #' @param tag Character. The Docker image tag. 73 | #' @param port Integer. The port to map. 74 | #' @param app_type Character. Either "r" or "python". 75 | #' @param env_vars Named character vector. Environment variables. 76 | #' @param detach Logical. Whether to run in detached mode. 77 | #' @param quiet Logical. Whether to suppress output. 78 | #' @param ... Additional arguments passed to processx. 79 | #' 80 | #' @return Character. Container ID if successful. 81 | #' 82 | #' @keywords internal 83 | run_with_docker <- function(tag, port, app_type, env_vars = NULL, detach = FALSE, quiet = FALSE, ...) { 84 | # Prepare docker run arguments 85 | run_args <- c("run") 86 | 87 | # Add platform flag if needed 88 | platform_flag <- get_platform_flag(app_type) 89 | if (length(platform_flag) > 0) { 90 | run_args <- c(run_args, platform_flag) 91 | } 92 | 93 | # Add port mapping 94 | run_args <- c(run_args, "-p", paste0(port, ":3838")) 95 | 96 | # Add environment variables if provided 97 | if (!is.null(env_vars) && length(env_vars) > 0) { 98 | cli::cli_alert_info("Setting {length(env_vars)} environment variables") 99 | for (env_name in names(env_vars)) { 100 | run_args <- c(run_args, "-e", paste0(env_name, "=", env_vars[env_name])) 101 | } 102 | } 103 | 104 | # Add detach flag if requested 105 | if (detach) { 106 | run_args <- c(run_args, "-d") 107 | } 108 | 109 | # Add the image tag 110 | run_args <- c(run_args, tag) 111 | 112 | # Execute command 113 | result <- execute_docker_command( 114 | "docker", 115 | run_args, 116 | quiet = quiet, 117 | echo = !quiet, 118 | error_on_status = FALSE, 119 | ... 120 | ) 121 | 122 | # Get container ID from output (if available) 123 | container_id <- NULL 124 | if (detach && result$status == 0 && nzchar(result$stdout)) { 125 | container_id <- trimws(result$stdout) 126 | } 127 | 128 | return(container_id) 129 | } 130 | 131 | #' Run a Docker container with a Shiny application 132 | #' 133 | #' @param app_dir Character. Path to the Shiny application directory with Docker configuration. 134 | #' @param tag Character. The tag for the Docker image to run. If NULL, a tag will be generated 135 | #' from the directory name (should match what was used in build_image). 136 | #' @param port Integer. The port to map to the container's exposed port. Default: 3838. 137 | #' @param detach Logical. If TRUE, run container in detached mode. Default: FALSE. 138 | #' @param env_vars Named character vector. Environment variables to pass to the container. 139 | #' @param docker_compose Logical. If TRUE, use docker-compose for running if available. Default: TRUE. 140 | #' @param quiet Logical. If TRUE, suppress Docker command output. Default: FALSE. 141 | #' @param force_port Logical. If TRUE, fail if requested port is unavailable. If FALSE, try to find an alternative port. Default: FALSE. 142 | #' @param ... Additional arguments passed to processx. 143 | #' 144 | #' @return For detached mode, invisibly returns the container ID if successful. 145 | #' For attached mode, the function will block until the container stops. 146 | #' 147 | #' @export 148 | #' 149 | #' @examples 150 | #' \dontrun{ 151 | #' # First create Docker configuration and build 152 | #' dockerize("path/to/my/shinyapp") 153 | #' build_image("path/to/my/shinyapp") 154 | #' 155 | #' # Run the container 156 | #' run_container("path/to/my/shinyapp") 157 | #' 158 | #' # Run detached on a different port 159 | #' run_container("path/to/my/shinyapp", port = 8080, detach = TRUE) 160 | #' } 161 | run_container <- function(app_dir, tag = NULL, port = 3838, 162 | detach = FALSE, env_vars = NULL, docker_compose = TRUE, 163 | quiet = FALSE, force_port = FALSE, ...) { 164 | # Validate inputs 165 | check_app_directory(app_dir) 166 | check_dockerfile(app_dir) 167 | 168 | 169 | # Prepare tag and app type 170 | if (is.null(tag)) { 171 | tag <- generate_image_tag(app_dir) 172 | } 173 | 174 | # Detect app type 175 | app_type <- detect_app_type(app_dir) 176 | 177 | # Check port availability and get an alternative if needed 178 | original_port <- port 179 | port <- handle_port_configuration(port, force_port) 180 | 181 | # Log port information clearly 182 | if (port != original_port) { 183 | cli::cli_alert_success("Using alternative port {.val {port}} instead of {.val {original_port}}") 184 | } 185 | 186 | # Update docker-compose.yml with the new port if needed 187 | if (port != 3838 && should_use_compose(app_dir, docker_compose)) { 188 | update_compose_port(app_dir, port) 189 | } 190 | 191 | # Determine running method 192 | container_id <- if (should_use_compose(app_dir, docker_compose)) { 193 | cli::cli_alert_info("Running container with docker-compose...") 194 | run_with_compose(app_dir, port, env_vars, detach, quiet, ...) 195 | } else { 196 | cli::cli_alert_info("Running container with docker run...") 197 | run_with_docker(tag, port, app_type, env_vars, detach, quiet, ...) 198 | } 199 | 200 | # Display access information 201 | if (detach) { 202 | cli::cli_alert_success("Container running in detached mode") 203 | app_url <- generate_app_url(app_type, port) 204 | cli::cli_alert_info("Access the Shiny app at: {.url {app_url}}") 205 | } 206 | 207 | return(invisible(container_id)) 208 | } 209 | 210 | #' Update port mapping in docker-compose.yml 211 | #' 212 | #' @param app_dir Character. Path to the application directory. 213 | #' @param port Integer. New port to use. 214 | #' 215 | #' @return Invisibly returns TRUE if successful. 216 | #' 217 | #' @keywords internal 218 | update_compose_port <- function(app_dir, port) { 219 | compose_path <- fs::path(app_dir, "docker-compose.yml") 220 | 221 | # Read compose file 222 | compose_data <- yaml::read_yaml(compose_path) 223 | 224 | # Update port in the first service (assumes single service) 225 | service_name <- names(compose_data$services)[1] 226 | current_ports <- compose_data$services[[service_name]]$ports 227 | 228 | # Find and replace the port mapping 229 | for (i in seq_along(current_ports)) { 230 | if (grepl("3838$", current_ports[i])) { 231 | compose_data$services[[service_name]]$ports[i] <- list(paste0(port, ":3838")) 232 | break 233 | } 234 | } 235 | 236 | # Write updated docker-compose.yml 237 | yaml::write_yaml(compose_data, compose_path) 238 | 239 | cli::cli_alert_info("Updated docker-compose.yml with new port mapping: {.val {port}}:3838") 240 | invisible(TRUE) 241 | } 242 | 243 | #' Find containers associated with an application 244 | #' 245 | #' @param app_dir Character. Path to the application directory. 246 | #' @param quiet Logical. Whether to suppress messages. 247 | #' @param ... Additional arguments passed to processx. 248 | #' 249 | #' @return Character vector. Container IDs. 250 | #' 251 | #' @keywords internal 252 | find_containers_for_app <- function(app_dir, quiet = FALSE, ...) { 253 | expected_image <- paste0("shiny-", tolower(fs::path_file(app_dir))) 254 | 255 | if (!quiet) { 256 | cli::cli_alert_info("Finding containers for image: {.val {expected_image}}") 257 | } 258 | 259 | find_result <- execute_docker_command( 260 | "docker", 261 | c("ps", "-a", "--filter", paste0("ancestor=", expected_image), "--format", "{{.ID}}"), 262 | quiet = TRUE, 263 | ... 264 | ) 265 | 266 | container_ids <- strsplit(trimws(find_result$stdout), "\\s+")[[1]] 267 | 268 | if (length(container_ids) == 0 || (length(container_ids) == 1 && container_ids[1] == "")) { 269 | if (!quiet) { 270 | cli::cli_alert_warning("No containers found for image: {.val {expected_image}}") 271 | } 272 | return(character(0)) 273 | } 274 | 275 | return(container_ids) 276 | } 277 | 278 | #' Stop a Docker container 279 | #' 280 | #' @param container_id Character. ID of the container to stop. 281 | #' @param quiet Logical. Whether to suppress output. 282 | #' @param ... Additional arguments passed to processx. 283 | #' 284 | #' @return Logical. TRUE if successful. 285 | #' 286 | #' @keywords internal 287 | stop_docker_container <- function(container_id, quiet = FALSE, ...) { 288 | # Run docker stop command 289 | result <- execute_docker_command( 290 | "docker", 291 | c("stop", container_id), 292 | quiet = quiet, 293 | echo = !quiet, 294 | error_on_status = FALSE, 295 | ... 296 | ) 297 | 298 | # Check if command was successful 299 | if (result$status != 0) { 300 | cli::cli_alert_warning(c( 301 | "Failed to stop container {.val {container_id}} (exit code {result$status}):", 302 | "x" = "{result$stderr}" 303 | )) 304 | return(FALSE) 305 | } 306 | 307 | cli::cli_alert_success("Container {.val {container_id}} stopped successfully") 308 | return(TRUE) 309 | } 310 | 311 | #' Stop running Docker containers for a Shiny application 312 | #' 313 | #' This function stops a running Docker container that was started with 314 | #' \code{run_container()} or \code{export()}. It can stop containers by ID 315 | #' or by finding containers associated with the specified application directory. 316 | #' If neither app_dir nor container_id is provided, it will stop all running containers. 317 | #' 318 | #' @param app_dir Character. Path to the Shiny application directory with Docker configuration. 319 | #' If provided, the function will attempt to find and stop containers based on the image name. 320 | #' @param container_id Character. ID or name of the container to stop. If NULL, 321 | #' the function will try to find containers based on app_dir. 322 | #' @param docker_compose Logical. If TRUE, use docker-compose for stopping if available. Default: TRUE. 323 | #' @param quiet Logical. If TRUE, suppress Docker command output. Default: FALSE. 324 | #' @param force Logical. If TRUE and stopping all containers, skip confirmation prompt. Default: FALSE. 325 | #' @param ... Additional arguments passed to processx. 326 | #' 327 | #' @return Invisibly returns TRUE if successful, FALSE otherwise. 328 | #' 329 | #' @export 330 | #' 331 | #' @examples 332 | #' \dontrun{ 333 | #' # First run a container 334 | #' result <- export("path/to/my/shinyapp", run = TRUE) 335 | #' 336 | #' # Stop by container ID 337 | #' stop_container(container_id = result$container_id) 338 | #' 339 | #' # Or stop by app directory 340 | #' stop_container("path/to/my/shinyapp") 341 | #' 342 | #' # Stop all running containers 343 | #' stop_container() 344 | #' } 345 | stop_container <- function(app_dir = NULL, container_id = NULL, 346 | docker_compose = TRUE, quiet = FALSE, force = FALSE, ...) { 347 | 348 | # Check if Docker is available 349 | check_docker_available() 350 | 351 | # Track success to determine if cleanup note should be shown 352 | success <- FALSE 353 | 354 | # Case 1: Use docker-compose if requested and available 355 | if (!is.null(app_dir) && docker_compose && should_use_compose(app_dir, docker_compose)) { 356 | success <- stop_with_compose(app_dir, quiet, ...) 357 | } 358 | # Case 2: Neither app_dir nor container_id provided - stop all containers 359 | else if (is.null(app_dir) && is.null(container_id)) { 360 | success <- stop_all_containers(quiet, force, ...) 361 | # Note is already shown in stop_all_containers when successful 362 | return(invisible(success)) 363 | } 364 | # Case 3: app_dir provided but no container_id 365 | else if (!is.null(app_dir) && is.null(container_id)) { 366 | success <- stop_containers_for_app(app_dir, quiet, force, ...) 367 | } 368 | # Case 4: container_id provided 369 | else if (!is.null(container_id)) { 370 | success <- stop_docker_container(container_id, quiet, ...) 371 | } 372 | # Shouldn't reach here, but for safety 373 | else { 374 | cli::cli_alert_warning("No containers specified to stop") 375 | return(invisible(FALSE)) 376 | } 377 | 378 | # Show cleanup note if containers were successfully stopped 379 | if (success) { 380 | cli::cli_alert_info("To remove stopped containers, use {.code cleanup_container()} function") 381 | } 382 | 383 | return(invisible(success)) 384 | } 385 | 386 | #' Stop containers using docker-compose 387 | #' 388 | #' @param app_dir Character. Path to the application directory. 389 | #' @param quiet Logical. Whether to suppress output. 390 | #' @param ... Additional arguments passed to processx. 391 | #' 392 | #' @return Logical. TRUE if successful. 393 | #' 394 | #' @keywords internal 395 | stop_with_compose <- function(app_dir, quiet = FALSE, ...) { 396 | cli::cli_alert_info("Stopping container with docker-compose...") 397 | 398 | # Run docker-compose stop (not down, which would remove containers) 399 | result <- execute_docker_command( 400 | "docker-compose", 401 | c("-f", fs::path(app_dir, "docker-compose.yml"), "stop"), 402 | quiet = quiet, 403 | echo = !quiet, 404 | error_on_status = FALSE, 405 | ... 406 | ) 407 | 408 | # Check if command was successful 409 | if (result$status != 0) { 410 | cli::cli_alert_warning(c( 411 | "Failed to stop container with docker-compose (exit code {result$status}):", 412 | "x" = "{result$stderr}" 413 | )) 414 | return(FALSE) 415 | } 416 | 417 | cli::cli_alert_success("Container stopped successfully with docker-compose") 418 | return(TRUE) 419 | } 420 | 421 | 422 | #' Stop all running Docker containers 423 | #' 424 | #' @param quiet Logical. Whether to suppress output. 425 | #' @param force Logical. Whether to skip confirmation. 426 | #' @param ... Additional arguments passed to processx. 427 | #' 428 | #' @return Logical. TRUE if successful. 429 | #' 430 | #' @keywords internal 431 | stop_all_containers <- function(quiet = FALSE, force = FALSE, ...) { 432 | # Find all running containers 433 | cli::cli_alert_info("Finding all running containers...") 434 | 435 | find_result <- execute_docker_command( 436 | "docker", 437 | c("ps", "--format", "{{.ID}}"), 438 | quiet = TRUE, 439 | ... 440 | ) 441 | 442 | # Get container IDs from output 443 | container_ids <- strsplit(trimws(find_result$stdout), "\\s+")[[1]] 444 | 445 | # Check if any containers were found 446 | if (length(container_ids) == 0 || (length(container_ids) == 1 && container_ids[1] == "")) { 447 | cli::cli_alert_info("No running containers found") 448 | return(invisible(TRUE)) 449 | } 450 | 451 | # Get container details for display 452 | details_result <- execute_docker_command( 453 | "docker", 454 | c("ps", "--format", "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}"), 455 | quiet = TRUE, 456 | ... 457 | ) 458 | 459 | # Display container details 460 | cli::cli_alert_info("Found {length(container_ids)} running container(s):") 461 | cli::cli_text(details_result$stdout) 462 | 463 | # Ask for confirmation unless force=TRUE 464 | if (!force) { 465 | cli::cli_alert_warning("This will stop ALL running containers!") 466 | response <- readline("Do you want to continue? (y/N): ") 467 | 468 | if (tolower(substr(response, 1, 1)) != "y") { 469 | cli::cli_alert_info("Operation cancelled") 470 | return(invisible(FALSE)) 471 | } 472 | } 473 | 474 | # Stop all containers 475 | cli::cli_alert_info("Stopping all containers...") 476 | 477 | # Count successful stops 478 | success_count <- 0 479 | 480 | for (id in container_ids) { 481 | result <- stop_docker_container(id, quiet, ...) 482 | if (result) { 483 | success_count <- success_count + 1 484 | } 485 | } 486 | 487 | # Report results 488 | if (success_count == length(container_ids)) { 489 | cli::cli_alert_success("All {length(container_ids)} container(s) stopped successfully") 490 | # Add note about cleanup_container() after all containers are stopped 491 | cli::cli_alert_info("To remove stopped containers, use {.code cleanup_container()} function") 492 | return(invisible(TRUE)) 493 | } else if (success_count > 0) { 494 | cli::cli_alert_warning("Stopped {success_count} out of {length(container_ids)} container(s)") 495 | # Add note about cleanup_container() 496 | cli::cli_alert_info("To remove stopped containers, use {.code cleanup_container()} function") 497 | return(invisible(TRUE)) 498 | } else { 499 | cli::cli_alert_danger("Failed to stop any containers") 500 | return(invisible(FALSE)) 501 | } 502 | } 503 | 504 | #' Stop containers for a specific application 505 | #' 506 | #' @param app_dir Character. Path to the application directory. 507 | #' @param quiet Logical. Whether to suppress output. 508 | #' @param force Logical. Whether to skip confirmation. 509 | #' @param ... Additional arguments passed to processx. 510 | #' 511 | #' @return Logical. TRUE if successful. 512 | #' 513 | #' @keywords internal 514 | stop_containers_for_app <- function(app_dir, quiet = FALSE, force = FALSE, ...) { 515 | check_app_directory(app_dir) 516 | 517 | # Find containers for this app 518 | container_ids <- find_containers_for_app(app_dir, quiet, ...) 519 | 520 | # Check if any containers were found 521 | if (length(container_ids) == 0) { 522 | cli::cli_alert_warning("No containers found for {.file {app_dir}}") 523 | return(invisible(FALSE)) 524 | } 525 | 526 | # If multiple containers found, handle selection 527 | if (length(container_ids) > 1) { 528 | return(handle_multiple_containers(container_ids, quiet, force, ...)) 529 | } else { 530 | # Just one container, stop it 531 | return(invisible(stop_docker_container(container_ids[1], quiet, ...))) 532 | } 533 | } 534 | 535 | #' Handle stopping multiple containers 536 | #' 537 | #' @param container_ids Character vector. Container IDs. 538 | #' @param quiet Logical. Whether to suppress output. 539 | #' @param force Logical. Whether to skip confirmation. 540 | #' @param ... Additional arguments passed to processx. 541 | #' 542 | #' @return Logical. TRUE if successful. 543 | #' 544 | #' @keywords internal 545 | handle_multiple_containers <- function(container_ids, quiet = FALSE, force = FALSE, ...) { 546 | cli::cli_alert_info("Multiple containers found:") 547 | 548 | # Get container details 549 | for (i in seq_along(container_ids)) { 550 | details_result <- execute_docker_command( 551 | "docker", 552 | c("ps", "-a", "--filter", paste0("id=", container_ids[i]), "--format", "{{.ID}}: {{.Status}}"), 553 | quiet = TRUE, 554 | ... 555 | ) 556 | cli::cli_bullets(paste0("[", i, "] ", trimws(details_result$stdout))) 557 | } 558 | 559 | # If force=TRUE, stop all containers 560 | if (force) { 561 | cli::cli_alert_info("Stopping all containers due to force=TRUE") 562 | all_success <- TRUE 563 | for (id in container_ids) { 564 | result <- stop_docker_container(id, quiet, ...) 565 | all_success <- all_success && result 566 | } 567 | return(invisible(all_success)) 568 | } 569 | 570 | # Let user choose which container to stop 571 | selection <- readline("Enter the number of the container to stop (or 'all' to stop all): ") 572 | 573 | if (selection == "all") { 574 | # Stop all containers 575 | all_success <- TRUE 576 | for (id in container_ids) { 577 | result <- stop_docker_container(id, quiet, ...) 578 | all_success <- all_success && result 579 | } 580 | if (all_success) { 581 | cli::cli_alert_success("All containers stopped successfully") 582 | } 583 | return(invisible(all_success)) 584 | } else { 585 | selection <- as.integer(selection) 586 | if (is.na(selection) || selection < 1 || selection > length(container_ids)) { 587 | cli::cli_abort("Invalid selection") 588 | } else { 589 | return(invisible(stop_docker_container(container_ids[selection], quiet, ...))) 590 | } 591 | } 592 | } -------------------------------------------------------------------------------- /R/docker-core.R: -------------------------------------------------------------------------------- 1 | #' Execute a Docker command with standardized error handling 2 | #' 3 | #' @param cmd Character. The command to execute (e.g., "docker", "docker-compose"). 4 | #' @param args Character vector. Arguments for the command. 5 | #' @param quiet Logical. If TRUE, suppress command output. Default: FALSE. 6 | #' @param echo Logical. Whether to echo command output. Default: !quiet. 7 | #' @param error_on_status Logical. Whether to error on non-zero exit status. Default: FALSE. 8 | #' @param ... Additional arguments passed to processx::run. 9 | #' 10 | #' @return The result of processx::run. 11 | #' 12 | #' @keywords internal 13 | execute_docker_command <- function(cmd, args, quiet = FALSE, echo = !quiet, 14 | error_on_status = FALSE, ...) { 15 | # Display command if not quiet 16 | if (!quiet) { 17 | cli::cli_alert_info("Executing: {.code {cmd} {paste(args, collapse = ' ')}}") 18 | } 19 | 20 | # Run command with standardized error handling 21 | result <- tryCatch({ 22 | processx::run( 23 | cmd, 24 | args = args, 25 | echo_cmd = FALSE, 26 | echo = echo, 27 | error_on_status = error_on_status, 28 | ... 29 | ) 30 | }, error = function(e) { 31 | cli::cli_abort(c( 32 | "Error executing {cmd} command", 33 | "x" = "{conditionMessage(e)}" 34 | )) 35 | }) 36 | 37 | # Check command status 38 | if (!error_on_status && result$status != 0) { 39 | cli::cli_abort(c( 40 | "{cmd} command failed with exit code {result$status}", 41 | "x" = "{result$stderr}" 42 | )) 43 | } 44 | 45 | return(result) 46 | } 47 | 48 | #' Check if Docker is available on the system 49 | #' 50 | #' @return Logical. TRUE if Docker is available in the system path. 51 | #' 52 | #' @keywords internal 53 | is_docker_available <- function() { 54 | tryCatch({ 55 | result <- processx::run("docker", "--version", error_on_status = FALSE) 56 | return(result$status == 0) 57 | }, error = function(e) { 58 | return(FALSE) 59 | }) 60 | } 61 | 62 | #' Check if Docker is available and abort if not 63 | #' 64 | #' @return Invisibly returns TRUE if Docker is available, otherwise aborts with an error message. 65 | #' 66 | #' @keywords internal 67 | check_docker_available <- function() { 68 | if (!is_docker_available()) { 69 | cli::cli_abort(c( 70 | "Docker is not available on your system or not in your PATH", 71 | "i" = "Make sure Docker is installed and available in your PATH", 72 | "i" = "You may need to restart your R session after installing Docker" 73 | )) 74 | } 75 | 76 | invisible(TRUE) 77 | } 78 | 79 | #' Check if Docker Compose is available 80 | #' 81 | #' @return Logical. TRUE if docker-compose command is available in the system path. 82 | #' 83 | #' @keywords internal 84 | is_docker_compose_available <- function() { 85 | tryCatch({ 86 | result <- processx::run("docker-compose", "--version", error_on_status = FALSE) 87 | return(result$status == 0) 88 | }, error = function(e) { 89 | return(FALSE) 90 | }) 91 | } 92 | 93 | #' Check if a directory exists and is valid 94 | #' 95 | #' @param app_dir Character. Path to check. 96 | #' 97 | #' @return Invisibly returns TRUE if valid, otherwise aborts with an error message. 98 | #' 99 | #' @keywords internal 100 | check_app_directory <- function(app_dir) { 101 | app_dir <- fs::path_abs(app_dir) 102 | 103 | if (!fs::dir_exists(app_dir)) { 104 | cli::cli_abort("Application directory does not exist: {.file {app_dir}}") 105 | } 106 | 107 | invisible(TRUE) 108 | } 109 | 110 | #' Check if a Dockerfile exists in the specified directory 111 | #' 112 | #' @param app_dir Character. Path to the application directory. 113 | #' 114 | #' @return Invisibly returns TRUE if exists, otherwise aborts with an error message. 115 | #' 116 | #' @keywords internal 117 | check_dockerfile <- function(app_dir) { 118 | dockerfile_path <- fs::path(app_dir, "Dockerfile") 119 | 120 | if (!fs::file_exists(dockerfile_path)) { 121 | cli::cli_abort(c( 122 | "Dockerfile not found in: {.file {app_dir}}", 123 | "i" = "Run {.code dockerize()} first to create Docker configuration." 124 | )) 125 | } 126 | 127 | invisible(TRUE) 128 | } 129 | 130 | #' Detect if system is running on ARM64 architecture 131 | #' 132 | #' @return Logical. TRUE if the system is running on ARM64 architecture. 133 | #' 134 | #' @keywords internal 135 | is_arm64 <- function() { 136 | # Check system architecture 137 | arch <- Sys.info()["machine"] 138 | 139 | # Return TRUE for ARM architectures (arm64, aarch64) 140 | return(grepl("arm|aarch", tolower(arch))) 141 | } 142 | 143 | #' Get platform flag for Docker if needed 144 | #' 145 | #' Returns platform flag for Docker commands if running on ARM64 and if app type is R. 146 | #' This is needed because Shiny Server isn't available natively for ARM64 yet. 147 | #' 148 | #' @param app_type Character. Either "r" or "python". 149 | #' 150 | #' @return Character vector. Platform flag for Docker commands or empty vector if not needed. 151 | #' 152 | #' @keywords internal 153 | get_platform_flag <- function(app_type) { 154 | if (is_arm64() && tolower(app_type) == "r") { 155 | cli::cli_alert_warning(c( 156 | "ARM64 architecture detected. Using x86_64 emulation for R Shiny container.", 157 | "i" = "This may impact performance until Shiny Server offers native ARM64 support." 158 | )) 159 | return(c("--platform", "linux/amd64")) 160 | } else { 161 | return(character(0)) 162 | } 163 | } -------------------------------------------------------------------------------- /R/export.R: -------------------------------------------------------------------------------- 1 | #' Create Docker configuration for a Shiny application 2 | #' 3 | #' @param app_dir Character. Path to the Shiny application directory. 4 | #' @param output_dir Character. Path where Docker configuration should be created. 5 | #' If NULL, files will be created in app_dir. 6 | #' @param app_type Character. Either "r" or "python". If NULL, it will be auto-detected. 7 | #' @param port Integer. The port to expose for the Shiny application. Default: 3838. 8 | #' @param dependencies Logical. Whether to automatically detect and include 9 | #' dependencies. Default: TRUE. 10 | #' @param custom_dockerfile Character. Path to a custom Dockerfile template to use. 11 | #' If NULL, the package's built-in templates will be used. 12 | #' @param env_vars Named character vector. Environment variables to include in 13 | #' the Docker configuration. 14 | #' @param ... Additional arguments passed to internal functions. 15 | #' 16 | #' @return Invisibly returns the path to the created Docker configuration. 17 | #' 18 | #' @export 19 | #' 20 | #' @examples 21 | #' \dontrun{ 22 | #' # Basic usage with an R Shiny app 23 | #' dockerize("path/to/my/shinyapp") 24 | #' 25 | #' # For a Python Shiny app 26 | #' dockerize("path/to/my/python/shinyapp", app_type = "python") 27 | #' 28 | #' # With custom port and environment variables 29 | #' dockerize("path/to/my/shinyapp", port = 8080, 30 | #' env_vars = c(API_KEY = "your-secret-key")) 31 | #' } 32 | dockerize <- function(app_dir, output_dir = NULL, app_type = NULL, port = 3838, 33 | dependencies = TRUE, custom_dockerfile = NULL, 34 | env_vars = NULL, ...) { 35 | # Validate inputs 36 | check_app_directory(app_dir) 37 | 38 | if (is.null(output_dir)) { 39 | output_dir <- app_dir 40 | } else { 41 | output_dir <- fs::path_abs(output_dir) 42 | fs::dir_create(output_dir, recurse = TRUE) 43 | } 44 | 45 | # Detect app type if not specified 46 | if (is.null(app_type)) { 47 | app_type <- detect_app_type(app_dir) 48 | } else { 49 | app_type <- tolower(app_type) 50 | if (!app_type %in% c("r", "python")) { 51 | cli::cli_abort("App type must be either {.val r} or {.val python}") 52 | } 53 | } 54 | 55 | # Detect dependencies if required 56 | if (dependencies) { 57 | cli::cli_alert_info("Detecting {ifelse(app_type == 'r', 'R', 'Python')} package dependencies...") 58 | deps <- detect_dependencies(app_dir, app_type) 59 | if (length(deps) > 0) { 60 | cli::cli_alert_success("Found {length(deps)} dependencies") 61 | } else { 62 | cli::cli_alert_info("No package dependencies detected") 63 | } 64 | } else { 65 | deps <- list() 66 | } 67 | 68 | # Create Dockerfile 69 | dockerfile_path <- create_dockerfile( 70 | output_dir, 71 | app_type = app_type, 72 | custom_template = custom_dockerfile, 73 | port = port, 74 | dependencies = deps, 75 | env_vars = env_vars, 76 | ... 77 | ) 78 | 79 | # Create docker-compose.yml 80 | compose_path <- create_docker_compose( 81 | output_dir, 82 | port = port, 83 | env_vars = env_vars, 84 | ... 85 | ) 86 | 87 | # Create .dockerignore 88 | ignore_path <- create_dockerignore(output_dir) 89 | 90 | cli::cli_alert_success("Docker configuration created successfully") 91 | cli::cli_bullets(c( 92 | " " = "Output directory: {.file {output_dir}}", 93 | " " = "Dockerfile: {.file {dockerfile_path}}", 94 | " " = "docker-compose.yml: {.file {compose_path}}", 95 | " " = ".dockerignore: {.file {ignore_path}}" 96 | )) 97 | 98 | invisible(output_dir) 99 | } 100 | 101 | #' Export a Shiny app to a Docker container 102 | #' 103 | #' This function takes a directory containing a Shiny application (either R or Python) 104 | #' and exports it to an appropriate Docker container. It handles the entire process 105 | #' including creating Docker configuration, building the Docker image, and optionally 106 | #' running the container. 107 | #' 108 | #' @param app_dir Character. Path to the Shiny application directory. 109 | #' @param output_dir Character. Path where Docker configuration should be created. 110 | #' If NULL, files will be created in app_dir. 111 | #' @param tag Character. The tag for the Docker image. If NULL, a tag will be generated 112 | #' from the directory name. 113 | #' @param port Integer. The port to expose for the Shiny application. Default: 3838. 114 | #' @param env_vars Named character vector. Environment variables to include in 115 | #' the Docker configuration. 116 | #' @param run Logical. Whether to run the container after building. Default: FALSE. 117 | #' @param detach Logical. If run=TRUE, whether to run in detached mode. Default: TRUE. 118 | #' @param quiet Logical. If TRUE, suppress Docker command output. Default: FALSE. 119 | #' @param ... Additional arguments passed to underlying functions. 120 | #' 121 | #' @return Invisibly returns a list with the paths to the created Docker files and 122 | #' container information if run=TRUE. 123 | #' 124 | #' @export 125 | #' 126 | #' @examples 127 | #' \dontrun{ 128 | #' # Basic usage 129 | #' export("path/to/my/shinyapp") 130 | #' 131 | #' # Export and run 132 | #' export("path/to/my/shinyapp", run = TRUE) 133 | #' 134 | #' # Custom configuration 135 | #' export("path/to/my/shinyapp", 136 | #' tag = "myorg/myapp:latest", 137 | #' port = 8080, 138 | #' env_vars = c(API_KEY = "secret-key"), 139 | #' run = TRUE) 140 | #' } 141 | export <- function(app_dir, output_dir = NULL, tag = NULL, port = 3838, 142 | env_vars = NULL, run = FALSE, detach = TRUE, quiet = FALSE, ...) { 143 | # Validate app_dir exists 144 | check_app_directory(app_dir) 145 | 146 | # Validate app has required files 147 | check_shiny_app_files(app_dir) 148 | 149 | # Step 1: Determine app type 150 | app_type <- detect_app_type(app_dir) 151 | 152 | # Report detected app type 153 | cli::cli_alert_info("Detected {.strong {ifelse(app_type == 'r', 'R', 'Python')}} Shiny application") 154 | 155 | # Step 2: Create Docker configuration 156 | cli::cli_alert_info("Creating Docker configuration...") 157 | docker_dir <- tryCatch({ 158 | dockerize( 159 | app_dir = app_dir, 160 | output_dir = output_dir, 161 | app_type = app_type, 162 | port = port, 163 | env_vars = env_vars, 164 | ... 165 | ) 166 | }, error = function(e) { 167 | cli::cli_abort(c( 168 | "Failed to create Docker configuration", 169 | "x" = "{e$message}" 170 | )) 171 | }) 172 | 173 | # Step 3: Build Docker image 174 | cli::cli_alert_info("Building Docker image...") 175 | image_tag <- tryCatch({ 176 | build_image( 177 | app_dir = docker_dir, 178 | tag = tag, 179 | quiet = quiet, 180 | ... 181 | ) 182 | }, error = function(e) { 183 | cli::cli_abort(c( 184 | "Failed to build Docker image", 185 | "x" = "{e$message}" 186 | )) 187 | }) 188 | 189 | # Step 4: Run container if requested 190 | container_id <- NULL 191 | if (run) { 192 | cli::cli_alert_info("Running Docker container...") 193 | container_id <- tryCatch({ 194 | run_container( 195 | app_dir = docker_dir, 196 | tag = image_tag, 197 | port = port, 198 | detach = detach, 199 | env_vars = env_vars, 200 | quiet = quiet, 201 | ... 202 | ) 203 | }, error = function(e) { 204 | cli::cli_alert_warning(c( 205 | "Failed to run Docker container", 206 | "x" = "{e$message}" 207 | )) 208 | return(NULL) 209 | }) 210 | } else { 211 | cli::cli_alert_info("To run the container, use:") 212 | cli::cli_code(glue::glue("run_container('{docker_dir}', port = {port}, detach = TRUE)")) 213 | } 214 | 215 | # Return information about export 216 | invisible(list( 217 | app_type = app_type, 218 | docker_dir = docker_dir, 219 | image_tag = image_tag, 220 | container_id = container_id 221 | )) 222 | } 223 | 224 | #' Clean up Docker containers and images created by shinydocker 225 | #' 226 | #' This function removes Docker containers and optionally images created by the 227 | #' shinydocker package. It can target a specific application or clean up all 228 | #' shinydocker-created containers and images. 229 | #' 230 | #' @param app_dir Character. Path to the Shiny application directory. If provided, 231 | #' only containers/images related to this application will be removed. 232 | #' If NULL, all containers/images created by shinydocker will be considered. 233 | #' @param remove_images Logical. If TRUE, also remove Docker images after removing containers. 234 | #' Default: FALSE. 235 | #' @param include_running Logical. If TRUE, stop and remove running containers. 236 | #' If FALSE, only stopped containers will be removed. Default: FALSE. 237 | #' @param force Logical. If TRUE, force removal without confirmation. Default: FALSE. 238 | #' @param prune Logical. If TRUE, run docker system prune after cleanup to remove unused data. 239 | #' Default: FALSE. 240 | #' @param quiet Logical. If TRUE, suppress Docker command output. Default: FALSE. 241 | #' @param ... Additional arguments passed to processx. 242 | #' 243 | #' @return Invisibly returns a list with counts of removed containers and images. 244 | #' 245 | #' @export 246 | #' 247 | #' @examples 248 | #' \dontrun{ 249 | #' # Clean up containers for a specific app 250 | #' cleanup_container("path/to/my/shinyapp") 251 | #' 252 | #' # Clean up all shinydocker containers and images 253 | #' cleanup_container(remove_images = TRUE) 254 | #' 255 | #' # Force cleanup of all containers including running ones 256 | #' cleanup_container(include_running = TRUE, force = TRUE) 257 | #' } 258 | cleanup_container <- function(app_dir = NULL, remove_images = FALSE, 259 | include_running = FALSE, force = FALSE, prune = FALSE, 260 | quiet = FALSE, ...) { 261 | # Check if Docker is available 262 | check_docker_available() 263 | 264 | # Initialize counters 265 | removed_containers <- 0 266 | removed_images <- 0 267 | 268 | # If app_dir is provided, validate and convert to absolute path 269 | if (!is.null(app_dir)) { 270 | check_app_directory(app_dir) 271 | 272 | # Generate image name based on app directory 273 | image_filter <- paste0("ancestor=shiny-", tolower(fs::path_file(app_dir))) 274 | cli::cli_alert_info("Targeting containers for app: {.file {app_dir}}") 275 | } else { 276 | # Filter for all shinydocker images - they all start with "shiny-" 277 | image_filter <- "ancestor=shiny-" 278 | cli::cli_alert_info("Targeting all shinydocker containers") 279 | } 280 | 281 | # Find all containers (running or not based on include_running) 282 | status_args <- if(include_running) c("ps", "-a") else c("ps", "--filter", "status=exited") 283 | 284 | # Find containers 285 | find_result <- execute_docker_command( 286 | "docker", 287 | c(status_args, "--filter", image_filter, "--format", "{{.ID}} {{.Status}}"), 288 | quiet = TRUE, 289 | error_on_status = FALSE, 290 | ... 291 | ) 292 | 293 | # Parse container data 294 | container_data <- trimws(find_result$stdout) 295 | if (container_data == "") { 296 | cli::cli_alert_info("No matching containers found to clean up") 297 | container_lines <- character(0) 298 | } else { 299 | container_lines <- strsplit(container_data, "\n")[[1]] 300 | } 301 | 302 | # Process found containers 303 | if (length(container_lines) > 0) { 304 | # Process and remove containers 305 | removed_containers <- process_containers(container_lines, include_running, force, quiet, ...) 306 | } 307 | 308 | # Find and remove images if requested 309 | if (remove_images) { 310 | removed_images <- remove_shinydocker_images(app_dir, force, quiet, ...) 311 | } 312 | 313 | # Run docker system prune if requested 314 | if (prune) { 315 | run_docker_prune(force, quiet, ...) 316 | } 317 | 318 | # Summary 319 | cli::cli_alert_success("Cleanup complete: removed {removed_containers} container(s) and {removed_images} image(s)") 320 | 321 | invisible(list(containers = removed_containers, images = removed_images)) 322 | } 323 | 324 | #' Process and remove containers 325 | #' 326 | #' @param container_lines Character vector. Container data lines. 327 | #' @param include_running Logical. Whether to include running containers. 328 | #' @param force Logical. Whether to force removal. 329 | #' @param quiet Logical. Whether to suppress output. 330 | #' @param ... Additional arguments passed to processx. 331 | #' 332 | #' @return Integer. Number of removed containers. 333 | #' 334 | #' @keywords internal 335 | process_containers <- function(container_lines, include_running, force, quiet, ...) { 336 | # Count running vs stopped containers 337 | running_containers <- grep("Up ", container_lines, value = TRUE) 338 | stopped_containers <- grep("Exited ", container_lines, value = TRUE) 339 | running_count <- length(running_containers) 340 | stopped_count <- length(stopped_containers) 341 | 342 | # Get container IDs 343 | all_container_ids <- sapply(strsplit(container_lines, " "), function(x) x[1]) 344 | running_container_ids <- if (length(running_containers) > 0) { 345 | sapply(strsplit(running_containers, " "), function(x) x[1]) 346 | } else { 347 | character(0) 348 | } 349 | stopped_container_ids <- if (length(stopped_containers) > 0) { 350 | sapply(strsplit(stopped_containers, " "), function(x) x[1]) 351 | } else { 352 | character(0) 353 | } 354 | 355 | # Show summary of what was found 356 | cli::cli_alert_info("Found {length(container_lines)} container(s): {running_count} running, {stopped_count} stopped") 357 | 358 | # For non-forced operations, ask for confirmation 359 | if (!force) { 360 | message <- "Do you want to proceed with cleanup?" 361 | 362 | if (include_running && running_count > 0) { 363 | message <- paste0(message, "\nWARNING: This will stop and remove ", running_count, " running container(s)!") 364 | } 365 | 366 | if (!utils::menu(c("Yes", "No"), title = message) == 1) { 367 | cli::cli_alert_info("Cleanup cancelled by user") 368 | return(0) 369 | } 370 | } 371 | 372 | # Stop running containers if included 373 | if (include_running && running_count > 0) { 374 | stop_running_containers(running_container_ids, quiet, ...) 375 | } 376 | 377 | # Remove containers 378 | removed_count <- 0 379 | target_container_ids <- if (include_running) all_container_ids else stopped_container_ids 380 | 381 | if (length(target_container_ids) > 0) { 382 | cli::cli_alert_info("Removing {length(target_container_ids)} container(s)...") 383 | 384 | for (container_id in target_container_ids) { 385 | rm_result <- execute_docker_command( 386 | "docker", 387 | c("rm", container_id), 388 | quiet = quiet, 389 | echo = !quiet, 390 | error_on_status = FALSE, 391 | ... 392 | ) 393 | 394 | if (rm_result$status == 0) { 395 | removed_count <- removed_count + 1 396 | if (!quiet) { 397 | cli::cli_alert_success("Removed container {.val {container_id}}") 398 | } 399 | } else { 400 | cli::cli_alert_warning("Failed to remove container {.val {container_id}}") 401 | } 402 | } 403 | } 404 | 405 | return(removed_count) 406 | } 407 | 408 | #' Stop running containers 409 | #' 410 | #' @param container_ids Character vector. Container IDs to stop. 411 | #' @param quiet Logical. Whether to suppress output. 412 | #' @param ... Additional arguments passed to processx. 413 | #' 414 | #' @return Invisibly returns TRUE. 415 | #' 416 | #' @keywords internal 417 | stop_running_containers <- function(container_ids, quiet, ...) { 418 | cli::cli_alert_info("Stopping {length(container_ids)} running container(s)...") 419 | 420 | for (container_id in container_ids) { 421 | stop_result <- execute_docker_command( 422 | "docker", 423 | c("stop", container_id), 424 | quiet = quiet, 425 | echo = !quiet, 426 | error_on_status = FALSE, 427 | ... 428 | ) 429 | 430 | if (stop_result$status != 0) { 431 | cli::cli_alert_warning("Failed to stop container {.val {container_id}}") 432 | } else if (!quiet) { 433 | cli::cli_alert_success("Stopped container {.val {container_id}}") 434 | } 435 | } 436 | 437 | invisible(TRUE) 438 | } 439 | 440 | #' Remove shinydocker images 441 | #' 442 | #' @param app_dir Character. Path to the application directory. If NULL, all shinydocker images. 443 | #' @param force Logical. Whether to force removal. 444 | #' @param quiet Logical. Whether to suppress output. 445 | #' @param ... Additional arguments passed to processx. 446 | #' 447 | #' @return Integer. Number of removed images. 448 | #' 449 | #' @keywords internal 450 | remove_shinydocker_images <- function(app_dir, force, quiet, ...) { 451 | # Adjust image filter for listing images 452 | if (!is.null(app_dir)) { 453 | image_name <- paste0("shiny-", tolower(fs::path_file(app_dir))) 454 | } else { 455 | image_name <- "shiny-" 456 | } 457 | 458 | # List images 459 | find_images_result <- execute_docker_command( 460 | "docker", 461 | c("images", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}", image_name), 462 | quiet = TRUE, 463 | error_on_status = FALSE, 464 | ... 465 | ) 466 | 467 | image_data <- trimws(find_images_result$stdout) 468 | if (image_data == "") { 469 | cli::cli_alert_info("No matching images found to remove") 470 | return(0) 471 | } 472 | 473 | image_lines <- strsplit(image_data, "\n")[[1]] 474 | cli::cli_alert_info("Found {length(image_lines)} image(s) to remove") 475 | 476 | # For non-forced image removal, ask for confirmation 477 | if (!force) { 478 | if (!utils::menu(c("Yes", "No"), title = "Do you want to remove these images?") == 1) { 479 | cli::cli_alert_info("Image removal cancelled by user") 480 | return(0) 481 | } 482 | } 483 | 484 | # Remove images 485 | removed_count <- 0 486 | for (image_line in image_lines) { 487 | parts <- strsplit(image_line, " ")[[1]] 488 | image_id <- parts[length(parts)] # Last part is the ID 489 | 490 | rmi_result <- execute_docker_command( 491 | "docker", 492 | c("rmi", image_id), 493 | quiet = quiet, 494 | echo = !quiet, 495 | error_on_status = FALSE, 496 | ... 497 | ) 498 | 499 | if (rmi_result$status == 0) { 500 | removed_count <- removed_count + 1 501 | if (!quiet) { 502 | cli::cli_alert_success("Removed image {.val {image_id}}") 503 | } 504 | } else { 505 | cli::cli_alert_warning("Failed to remove image {.val {image_id}}") 506 | } 507 | } 508 | 509 | return(removed_count) 510 | } 511 | 512 | #' Run docker system prune 513 | #' 514 | #' @param force Logical. Whether to force prune. 515 | #' @param quiet Logical. Whether to suppress output. 516 | #' @param ... Additional arguments passed to processx. 517 | #' 518 | #' @return Invisibly returns TRUE. 519 | #' 520 | #' @keywords internal 521 | run_docker_prune <- function(force, quiet, ...) { 522 | cli::cli_alert_info("Running docker system prune...") 523 | 524 | prune_args <- c("system", "prune") 525 | if (force) { 526 | prune_args <- c(prune_args, "-f") 527 | } 528 | 529 | prune_result <- execute_docker_command( 530 | "docker", 531 | prune_args, 532 | quiet = quiet, 533 | echo = !quiet, 534 | error_on_status = FALSE, 535 | ... 536 | ) 537 | 538 | if (prune_result$status == 0) { 539 | cli::cli_alert_success("Docker system prune completed successfully") 540 | } else { 541 | cli::cli_alert_warning("Docker system prune failed") 542 | } 543 | 544 | invisible(TRUE) 545 | } -------------------------------------------------------------------------------- /R/image.R: -------------------------------------------------------------------------------- 1 | #' Generate a default Docker image tag based on app directory 2 | #' 3 | #' @param app_dir Character. Path to the Shiny application directory. 4 | #' 5 | #' @return Character. Generated tag. 6 | #' 7 | #' @keywords internal 8 | generate_image_tag <- function(app_dir) { 9 | app_name <- tolower(fs::path_file(app_dir)) 10 | # Sanitize the app name to remove any characters that might cause issues 11 | app_name <- gsub("[^a-z0-9_.-]", "", app_name) 12 | return(paste0("shiny-", app_name, ":latest")) 13 | } 14 | 15 | #' Prepare Docker build arguments 16 | #' 17 | #' @param app_dir Character. Path to application directory. 18 | #' @param tag Character. Image tag. 19 | #' @param build_args Named character vector. Additional build arguments. 20 | #' @param quiet Logical. Whether to use quiet mode. 21 | #' 22 | #' @return Character vector of build arguments. 23 | #' 24 | #' @keywords internal 25 | prepare_build_arguments <- function(app_dir, tag, build_args = NULL, quiet = FALSE) { 26 | app_type <- detect_app_type(app_dir) 27 | platform_flag <- get_platform_flag(app_type) 28 | 29 | # Prepare docker build arguments 30 | build_args_vec <- c("build", "-t", tag) 31 | 32 | # Add platform flag if needed 33 | if (length(platform_flag) > 0) { 34 | build_args_vec <- c(build_args_vec, platform_flag) 35 | } 36 | 37 | # Add build args if provided 38 | if (!is.null(build_args) && length(build_args) > 0) { 39 | for (arg_name in names(build_args)) { 40 | build_args_vec <- c(build_args_vec, "--build-arg", 41 | paste0(arg_name, "=", build_args[arg_name])) 42 | } 43 | } 44 | 45 | # Add quietness flag if requested 46 | if (quiet) { 47 | build_args_vec <- c(build_args_vec, "--quiet") 48 | } 49 | 50 | # Add context path (the application directory) 51 | build_args_vec <- c(build_args_vec, app_dir) 52 | 53 | return(build_args_vec) 54 | } 55 | 56 | #' Build Docker image for a Shiny application 57 | #' 58 | #' @param app_dir Character. Path to the Shiny application directory with Docker configuration. 59 | #' @param tag Character. The tag for the Docker image. If NULL, a tag will be generated 60 | #' from the directory name. 61 | #' @param build_args Named character vector. Additional build arguments to pass to Docker. 62 | #' @param quiet Logical. If TRUE, suppress Docker build output. Default: FALSE. 63 | #' @param ... Additional arguments passed to processx. 64 | #' 65 | #' @return Invisibly returns the image ID if successful. 66 | #' 67 | #' @export 68 | #' 69 | #' @examples 70 | #' \dontrun{ 71 | #' # First create Docker configuration 72 | #' dockerize("path/to/my/shinyapp") 73 | #' 74 | #' # Then build the image 75 | #' build_image("path/to/my/shinyapp") 76 | #' 77 | #' # With a custom tag 78 | #' build_image("path/to/my/shinyapp", tag = "myorg/myapp:latest") 79 | #' } 80 | build_image <- function(app_dir, tag = NULL, build_args = NULL, quiet = FALSE, ...) { 81 | # Validate inputs 82 | check_app_directory(app_dir) 83 | check_dockerfile(app_dir) 84 | 85 | # Generate tag if not provided 86 | if (is.null(tag)) { 87 | tag <- generate_image_tag(app_dir) 88 | } 89 | 90 | # Prepare build arguments 91 | build_args_vec <- prepare_build_arguments(app_dir, tag, build_args, quiet) 92 | 93 | # Show command if not quiet 94 | if (!quiet) { 95 | cli::cli_alert_info("Building Docker image: {.val {tag}}") 96 | cli::cli_process_start("Building image") 97 | } 98 | 99 | # Execute build command 100 | result <- execute_docker_command( 101 | "docker", 102 | build_args_vec, 103 | quiet = quiet, 104 | ... 105 | ) 106 | 107 | # Stop process indicator if we were showing it 108 | if (!quiet) { 109 | cli::cli_process_done() 110 | } 111 | 112 | cli::cli_alert_success("Docker image built successfully: {.val {tag}}") 113 | invisible(tag) 114 | } -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' Check if a port is available on localhost 2 | #' 3 | #' @param port Integer. The port to check. 4 | #' 5 | #' @return Logical. TRUE if the port is available, FALSE if already in use. 6 | #' 7 | #' @keywords internal 8 | is_port_available <- function(port) { 9 | tryCatch({ 10 | serverSocket <- get("serverSocket", envir = baseenv(), 11 | mode = "function", inherits = FALSE) 12 | # Try to create a server socket on the port 13 | con <- serverSocket(port) 14 | close(con) 15 | TRUE # Success means port is available 16 | }, error = function(e) { 17 | FALSE # Error means port is in use 18 | }) 19 | } 20 | 21 | #' Find an available port starting from a given port 22 | #' 23 | #' @param start_port Integer. The port to start checking from. 24 | #' @param max_tries Integer. Maximum number of ports to check. Default: 100. 25 | #' 26 | #' @return Integer. An available port or NULL if no port found. 27 | #' 28 | #' @keywords internal 29 | find_available_port <- function(start_port, max_tries = 100) { 30 | for (offset in 0:(max_tries - 1)) { 31 | port <- start_port + offset 32 | if (is_port_available(port)) { 33 | return(port) 34 | } 35 | } 36 | return(NULL) # No available port found 37 | } 38 | 39 | #' Handle port configuration and availability 40 | #' 41 | #' @param port Integer. Requested port. 42 | #' @param force_port Logical. Whether to enforce the requested port. 43 | #' 44 | #' @return Integer. The port to use, either the requested one or an alternative. 45 | #' 46 | #' @keywords internal 47 | handle_port_configuration <- function(port, force_port = FALSE) { 48 | if (!is_port_available(port)) { 49 | if (force_port) { 50 | cli::cli_abort(c( 51 | "Port {.val {port}} is already in use", 52 | "i" = "Specify a different port or set force_port=FALSE to auto-select an available port." 53 | )) 54 | } 55 | 56 | cli::cli_alert_warning("Port {.val {port}} is already in use") 57 | 58 | # Try to find an alternative port with more attempts 59 | alternative_port <- find_available_port(port + 1, max_tries = 100) 60 | 61 | if (is.null(alternative_port)) { 62 | cli::cli_abort(c( 63 | "Port {.val {port}} is already in use and no alternative ports available in range {.val {port+1}} to {.val {port+100}}", 64 | "i" = "Please specify a different port range or close applications using these ports." 65 | )) 66 | } 67 | 68 | cli::cli_alert_info("Using alternative port {.val {alternative_port}} instead") 69 | return(alternative_port) 70 | } 71 | 72 | return(port) 73 | } 74 | 75 | #' Generate app URL based on app type and port 76 | #' 77 | #' @param app_type Character. Either "r" or "python". 78 | #' @param port Integer. Port number. 79 | #' 80 | #' @return Character. URL to access the app. 81 | #' 82 | #' @keywords internal 83 | generate_app_url <- function(app_type, port) { 84 | # For R apps, Shiny Server serves the app at /app/ 85 | # For Python apps, the app is served at the root 86 | if (app_type == "r") { 87 | return(paste0("http://localhost:", port, "/app/")) 88 | } else { 89 | return(paste0("http://localhost:", port)) 90 | } 91 | } -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | ```{r, include = FALSE} 7 | knitr::opts_chunk$set( 8 | collapse = TRUE, 9 | comment = "#>", 10 | fig.path = "man/figures/README-", 11 | out.width = "100%" 12 | ) 13 | ``` 14 | 15 | # shinydocker hex logo for shinydocker 16 | 17 | 18 | [![R-CMD-check](https://github.com/coatless-rpkg/shinydocker/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/coatless-rpkg/shinydocker/actions/workflows/R-CMD-check.yaml) 19 | ![Prototype](https://img.shields.io/badge/Status-Prototype-orange) 20 | ![Experimental](https://img.shields.io/badge/Status-Experimental-blue) 21 | 22 | 23 | `shinydocker` simplifies containerizing Shiny applications by automatically generating Docker configurations, building images, and managing containers. It supports both R and Python Shiny apps with intelligent detection of app type and dependencies. 24 | 25 | > [!IMPORTANT] 26 | > 27 | > This package is currently in the prototype/experimental stage. It is not yet 28 | > available on CRAN and may have bugs or limitations. 29 | 30 | ## Installation 31 | 32 | You can install the development version of `shinydocker` from GitHub with: 33 | 34 | ```{r} 35 | #| label: install-shinydocker 36 | #| eval: false 37 | # install.packages("remotes") 38 | remotes::install_github("coatless-rpkg/shinydocker") 39 | ``` 40 | 41 | ### Prerequisites 42 | 43 | - R (>= 4.4.0) 44 | - [Docker](https://www.docker.com/products/docker-desktop) installed and running (no login required) 45 | - Optionally, [Docker Compose](https://docs.docker.com/compose/install/) for more advanced container management 46 | 47 | ## Quick Start 48 | 49 | With `shinydocker`, you can containerize your Shiny app in just a few lines of code. 50 | Here's two ways to get started with the package! 51 | 52 | ### One-Command Export 53 | 54 | The simplest way to containerize a Shiny app is to use the `shinydocker::export()` function: 55 | 56 | ```{r} 57 | #| label: quick-export 58 | #| eval: false 59 | library(shinydocker) 60 | 61 | # Export app to Docker with a single command (detects app type automatically) 62 | shinydocker::export("path/to/your/shinyapp", run = TRUE) 63 | ``` 64 | 65 | ### Example: Converting the "Hello World" Shiny App 66 | 67 | To get started, let's convert the classic "Hello World" Shiny app to a Docker container: 68 | 69 | ```{r} 70 | #| label: export-hello-world 71 | #| eval: false 72 | # Copy the Hello World example from the shiny package 73 | app_dir <- "hello_world_app" 74 | system.file("examples", "01_hello", package = "shiny") |> 75 | fs::dir_copy(app_dir, overwrite = TRUE) 76 | 77 | # Export to Docker and run 78 | shinydocker::export(app_dir, run = TRUE, detach = TRUE) 79 | 80 | # The console will show a URL where you can access your containerized app 81 | 82 | # Stop the container when done 83 | shinydocker::stop_container() 84 | ``` 85 | 86 | ### Step-by-Step Workflow 87 | 88 | For more control over the containerization process: 89 | 90 | ```{r} 91 | #| label: step-by-step 92 | #| eval: false 93 | library(shinydocker) 94 | 95 | # 1. Create Docker configuration 96 | shinydocker::dockerize("path/to/your/shinyapp") 97 | 98 | # 2. Build Docker image 99 | shinydocker::build_image("path/to/your/shinyapp") 100 | 101 | # 3. Run the container on port 8080 102 | shinydocker::run_container("path/to/your/shinyapp", port = 8080, detach = TRUE) 103 | 104 | # 4. When done, stop the container 105 | shinydocker::stop_container("path/to/your/shinyapp") 106 | ``` 107 | 108 | 109 | ### R or Python Shiny Apps 110 | 111 | `shinydocker` automatically detects and containerizes either R or Python Shiny apps 112 | by detecting the app type and dependencies in the app directory. The app type 113 | is determined by the presence of either a `app.R`/`server.R`/`ui.R` file for R Shiny apps 114 | or a `app.py`/`server.py`/`ui.py` file for Python Shiny apps. Dependencies are detected 115 | automatically from the app source for R Shiny apps and a `requirements.txt` file for Python Shiny apps. 116 | 117 | Therefore, you can use the same `export()` function for both R and Python Shiny apps: 118 | 119 | ```{r} 120 | #| label: python-shiny 121 | #| eval: false 122 | shinydocker::export("path/to/your/python_shinyapp", run = TRUE) 123 | ``` 124 | 125 | 126 | ### Examples 127 | 128 | More examples are available in the package's [`inst/examples/shiny`](inst/examples/shiny) directory. 129 | 130 | ## Advanced Configuration 131 | 132 | For more advanced use cases, you can customize the containerization process with additional options. 133 | You can pass these options to the `dockerize()` function. 134 | 135 | ### Environment Variables 136 | 137 | Pass sensitive information or configuration to your Shiny app: 138 | 139 | ```{r} 140 | #| label: env-vars 141 | #| eval: false 142 | # Add environment variables to the container 143 | shinydocker::dockerize("path/to/your/shinyapp", 144 | env_vars = c( 145 | API_KEY = "your-secret-key", 146 | DB_HOST = "database.example.com", 147 | LOG_LEVEL = "INFO" 148 | )) 149 | ``` 150 | 151 | ### Custom Port Mapping 152 | 153 | ```{r} 154 | #| label: custom-port 155 | #| eval: false 156 | # Run the container on a specific port 157 | shinydocker::run_container("path/to/your/shinyapp", port = 3000) 158 | ``` 159 | 160 | ### Custom Dockerfile Template 161 | 162 | For advanced customization, you can provide your own Dockerfile template: 163 | 164 | ```{r} 165 | #| label: custom-dockerfile 166 | #| eval: false 167 | # Use a custom Dockerfile template 168 | shinydocker::dockerize("path/to/your/shinyapp", 169 | custom_dockerfile = "path/to/custom/Dockerfile") 170 | ``` 171 | 172 | ## Diagnostic Tools 173 | 174 | `shinydocker` provides two situation report (sitrep) functions that offer 175 | diagnostic information about your Docker environment and app containerization readiness. 176 | 177 | ### Check Docker Environment 178 | 179 | 180 | ```{r} 181 | #| label: check-docker 182 | #| eval: false 183 | # Check if Docker is correctly set up 184 | shinydocker::sitrep_docker() 185 | ``` 186 | 187 | ### Analyze App Containerization Readiness 188 | 189 | ```{r} 190 | #| label: check-app 191 | #| eval: false 192 | # Check if your app is ready for containerization 193 | shinydocker::sitrep_app_conversion("path/to/your/shinyapp") 194 | ``` 195 | 196 | ## Container Management 197 | 198 | `shinydocker` provides functions to manage your Shiny app containers so that 199 | you can start, stop, and clean up containers with ease without needing to remember 200 | Docker commands or jump into terminal. 201 | 202 | ### Running Containers 203 | 204 | ```{r} 205 | #| label: run-containers 206 | #| eval: false 207 | # Run with docker-compose (if available) 208 | shinydocker::run_container("path/to/your/shinyapp", docker_compose = TRUE) 209 | 210 | # Run with plain docker 211 | shinydocker::run_container("path/to/your/shinyapp", docker_compose = FALSE) 212 | ``` 213 | 214 | ### Stopping Containers 215 | 216 | ```{r} 217 | #| label: stop-containers 218 | #| eval: false 219 | # Stop container for a specific app 220 | shinydocker::stop_container("path/to/your/shinyapp") 221 | 222 | # Stop all running containers 223 | shinydocker::stop_container() 224 | ``` 225 | 226 | ### Cleanup 227 | 228 | ```{r} 229 | #| label: cleanup 230 | #| eval: false 231 | # Remove containers for a specific app 232 | shinydocker::cleanup_container("path/to/your/shinyapp") 233 | 234 | # Remove all shinydocker containers and images 235 | shinydocker::cleanup_container(remove_images = TRUE, force = TRUE) 236 | 237 | # Clean up and run docker system prune 238 | shinydocker::cleanup_container(prune = TRUE) 239 | ``` 240 | 241 | 242 | ## Troubleshooting 243 | 244 | If you encounter issues: 245 | 246 | 1. Run `shinydocker::sitrep_docker()` to check Docker installation 247 | 2. Run `shinydocker::sitrep_app_conversion("path/to/your/shinyapp")` to analyze app issues 248 | 3. Check for port conflicts with `shinydocker::is_port_available(3838)` 249 | 250 | Need more help? Consider opening an [issue](https://github.com/coatless-rpkg/shinydocker/issues/new) on the repository. 251 | 252 | ## Citation 253 | 254 | If you use `shinydocker` in your research or project, please consider citing it: 255 | 256 | ```{r} 257 | #| label: citation 258 | #| eval: false 259 | citation("shinydocker") 260 | ``` 261 | 262 | ## License 263 | 264 | AGPL (>= 3) 265 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # shinydocker hex logo for shinydocker 5 | 6 | 7 | 8 | [![R-CMD-check](https://github.com/coatless-rpkg/shinydocker/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/coatless-rpkg/shinydocker/actions/workflows/R-CMD-check.yaml) 9 | ![Prototype](https://img.shields.io/badge/Status-Prototype-orange) 10 | ![Experimental](https://img.shields.io/badge/Status-Experimental-blue) 11 | 12 | 13 | `shinydocker` simplifies containerizing Shiny applications by 14 | automatically generating Docker configurations, building images, and 15 | managing containers. It supports both R and Python Shiny apps with 16 | intelligent detection of app type and dependencies. 17 | 18 | > \[!IMPORTANT\] 19 | > 20 | > This package is currently in the prototype/experimental stage. It is 21 | > not yet available on CRAN and may have bugs or limitations. 22 | 23 | ## Installation 24 | 25 | You can install the development version of `shinydocker` from GitHub 26 | with: 27 | 28 | ``` r 29 | # install.packages("remotes") 30 | remotes::install_github("coatless-rpkg/shinydocker") 31 | ``` 32 | 33 | ### Prerequisites 34 | 35 | - R (\>= 4.4.0) 36 | - [Docker](https://www.docker.com/products/docker-desktop) installed and 37 | running (no login required) 38 | - Optionally, [Docker 39 | Compose](https://docs.docker.com/compose/install/) for more advanced 40 | container management 41 | 42 | ## Quick Start 43 | 44 | With `shinydocker`, you can containerize your Shiny app in just a few 45 | lines of code. Here’s two ways to get started with the package! 46 | 47 | ### One-Command Export 48 | 49 | The simplest way to containerize a Shiny app is to use the 50 | `shinydocker::export()` function: 51 | 52 | ``` r 53 | library(shinydocker) 54 | 55 | # Export app to Docker with a single command (detects app type automatically) 56 | shinydocker::export("path/to/your/shinyapp", run = TRUE) 57 | ``` 58 | 59 | ### Example: Converting the “Hello World” Shiny App 60 | 61 | To get started, let’s convert the classic “Hello World” Shiny app to a 62 | Docker container: 63 | 64 | ``` r 65 | # Copy the Hello World example from the shiny package 66 | app_dir <- "hello_world_app" 67 | system.file("examples", "01_hello", package = "shiny") |> 68 | fs::dir_copy(app_dir, overwrite = TRUE) 69 | 70 | # Export to Docker and run 71 | shinydocker::export(app_dir, run = TRUE, detach = TRUE) 72 | 73 | # The console will show a URL where you can access your containerized app 74 | 75 | # Stop the container when done 76 | shinydocker::stop_container() 77 | ``` 78 | 79 | ### Step-by-Step Workflow 80 | 81 | For more control over the containerization process: 82 | 83 | ``` r 84 | library(shinydocker) 85 | 86 | # 1. Create Docker configuration 87 | shinydocker::dockerize("path/to/your/shinyapp") 88 | 89 | # 2. Build Docker image 90 | shinydocker::build_image("path/to/your/shinyapp") 91 | 92 | # 3. Run the container on port 8080 93 | shinydocker::run_container("path/to/your/shinyapp", port = 8080, detach = TRUE) 94 | 95 | # 4. When done, stop the container 96 | shinydocker::stop_container("path/to/your/shinyapp") 97 | ``` 98 | 99 | ### R or Python Shiny Apps 100 | 101 | `shinydocker` automatically detects and containerizes either R or Python 102 | Shiny apps by detecting the app type and dependencies in the app 103 | directory. The app type is determined by the presence of either a 104 | `app.R`/`server.R`/`ui.R` file for R Shiny apps or a 105 | `app.py`/`server.py`/`ui.py` file for Python Shiny apps. Dependencies 106 | are detected automatically from the app source for R Shiny apps and a 107 | `requirements.txt` file for Python Shiny apps. 108 | 109 | Therefore, you can use the same `export()` function for both R and 110 | Python Shiny apps: 111 | 112 | ``` r 113 | shinydocker::export("path/to/your/python_shinyapp", run = TRUE) 114 | ``` 115 | 116 | ### Examples 117 | 118 | More examples are available in the package’s 119 | [`inst/examples/shiny`](inst/examples/shiny) directory. 120 | 121 | ## Advanced Configuration 122 | 123 | For more advanced use cases, you can customize the containerization 124 | process with additional options. You can pass these options to the 125 | `dockerize()` function. 126 | 127 | ### Environment Variables 128 | 129 | Pass sensitive information or configuration to your Shiny app: 130 | 131 | ``` r 132 | # Add environment variables to the container 133 | shinydocker::dockerize("path/to/your/shinyapp", 134 | env_vars = c( 135 | API_KEY = "your-secret-key", 136 | DB_HOST = "database.example.com", 137 | LOG_LEVEL = "INFO" 138 | )) 139 | ``` 140 | 141 | ### Custom Port Mapping 142 | 143 | ``` r 144 | # Run the container on a specific port 145 | shinydocker::run_container("path/to/your/shinyapp", port = 3000) 146 | ``` 147 | 148 | ### Custom Dockerfile Template 149 | 150 | For advanced customization, you can provide your own Dockerfile 151 | template: 152 | 153 | ``` r 154 | # Use a custom Dockerfile template 155 | shinydocker::dockerize("path/to/your/shinyapp", 156 | custom_dockerfile = "path/to/custom/Dockerfile") 157 | ``` 158 | 159 | ## Diagnostic Tools 160 | 161 | `shinydocker` provides two situation report (sitrep) functions that 162 | offer diagnostic information about your Docker environment and app 163 | containerization readiness. 164 | 165 | ### Check Docker Environment 166 | 167 | ``` r 168 | # Check if Docker is correctly set up 169 | shinydocker::sitrep_docker() 170 | ``` 171 | 172 | ### Analyze App Containerization Readiness 173 | 174 | ``` r 175 | # Check if your app is ready for containerization 176 | shinydocker::sitrep_app_conversion("path/to/your/shinyapp") 177 | ``` 178 | 179 | ## Container Management 180 | 181 | `shinydocker` provides functions to manage your Shiny app containers so 182 | that you can start, stop, and clean up containers with ease without 183 | needing to remember Docker commands or jump into terminal. 184 | 185 | ### Running Containers 186 | 187 | ``` r 188 | # Run with docker-compose (if available) 189 | shinydocker::run_container("path/to/your/shinyapp", docker_compose = TRUE) 190 | 191 | # Run with plain docker 192 | shinydocker::run_container("path/to/your/shinyapp", docker_compose = FALSE) 193 | ``` 194 | 195 | ### Stopping Containers 196 | 197 | ``` r 198 | # Stop container for a specific app 199 | shinydocker::stop_container("path/to/your/shinyapp") 200 | 201 | # Stop all running containers 202 | shinydocker::stop_container() 203 | ``` 204 | 205 | ### Cleanup 206 | 207 | ``` r 208 | # Remove containers for a specific app 209 | shinydocker::cleanup_container("path/to/your/shinyapp") 210 | 211 | # Remove all shinydocker containers and images 212 | shinydocker::cleanup_container(remove_images = TRUE, force = TRUE) 213 | 214 | # Clean up and run docker system prune 215 | shinydocker::cleanup_container(prune = TRUE) 216 | ``` 217 | 218 | ## Troubleshooting 219 | 220 | If you encounter issues: 221 | 222 | 1. Run `shinydocker::sitrep_docker()` to check Docker installation 223 | 2. Run `shinydocker::sitrep_app_conversion("path/to/your/shinyapp")` to 224 | analyze app issues 225 | 3. Check for port conflicts with `shinydocker::is_port_available(3838)` 226 | 227 | Need more help? Consider opening an 228 | [issue](https://github.com/coatless-rpkg/shinydocker/issues/new) on the 229 | repository. 230 | 231 | ## Citation 232 | 233 | If you use `shinydocker` in your research or project, please consider 234 | citing it: 235 | 236 | ``` r 237 | citation("shinydocker") 238 | ``` 239 | 240 | ## License 241 | 242 | AGPL (\>= 3) 243 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: ~ 2 | template: 3 | bootstrap: 5 4 | 5 | -------------------------------------------------------------------------------- /inst/demo-py-shiny-containerization.R: -------------------------------------------------------------------------------- 1 | # Quick demo of containerizing a simple Shiny for Python app 2 | 3 | ## Setup a temporary directory that gets destructed after the session ---- 4 | app_dir <- tempfile() 5 | dir.create(app_dir) 6 | 7 | ## Create a simple Shiny for Python app from a template ---- 8 | writeLines( 9 | readLines(system.file("examples", "shiny", "python", "hello-docker-plain", "app.py", package = "shinydocker")), 10 | file.path(app_dir, "app.py") 11 | ) 12 | 13 | ## Export the app ---- 14 | shinydocker::export(app_dir, run = TRUE, detach = TRUE) 15 | 16 | # Stop the container 17 | shinydocker::stop_container(app_dir) 18 | 19 | # Restart the container: 20 | shinydocker::run_container(app_dir, detach = TRUE) 21 | 22 | ## Alternatively, steps can be run separately ---- 23 | # Create Docker configuration 24 | # shinydocker::dockerize(app_dir) 25 | # Build Docker image 26 | # shinydocker::build_image(app_dir) 27 | # Run the containerized app 28 | # shinydocker::run_container(app_dir, detach = TRUE) 29 | -------------------------------------------------------------------------------- /inst/demo-r-shiny-containerization.R: -------------------------------------------------------------------------------- 1 | # Quick demo of containerizing a simple R Shiny app 2 | 3 | ## Create a simple R Shiny app ---- 4 | app_dir <- tempfile() 5 | dir.create(app_dir) 6 | writeLines( 7 | readLines(system.file("examples", "shiny", "r", "hello-docker-ggplot2", "app.R", package = "shinydocker")), 8 | file.path(app_dir, "app.R") 9 | ) 10 | 11 | ## Export the app ---- 12 | container <- shinydocker::export(app_dir, run = TRUE, detach = TRUE) 13 | 14 | ## Stop the container ---- 15 | shinydocker::stop_container() 16 | 17 | # Alternatively, steps can be run separately ---- 18 | 19 | ## Create Docker configuration ---- 20 | dockerize(app_dir) 21 | 22 | ## Build Docker image ---- 23 | build_image(app_dir) 24 | 25 | ## Run the containerized app ---- 26 | run_container(app_dir, detach = TRUE) 27 | -------------------------------------------------------------------------------- /inst/examples/shiny/python/hello-docker-plain/app.py: -------------------------------------------------------------------------------- 1 | from shiny import App, ui, render 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | app_ui = ui.page_fluid( 6 | ui.panel_title("Hello Docker"), 7 | ui.layout_sidebar( 8 | ui.sidebar( 9 | ui.input_slider("obs", "Number of observations:", min=1, max=1000, value=500) 10 | ), 11 | ui.output_plot("distPlot") 12 | ) 13 | ) 14 | 15 | def server(input, output, session): 16 | @output 17 | @render.plot 18 | def distPlot(): 19 | data = np.random.normal(size=input.obs()) 20 | fig, ax = plt.subplots() 21 | ax.hist(data) 22 | return fig 23 | 24 | app = App(app_ui, server) 25 | -------------------------------------------------------------------------------- /inst/examples/shiny/r/hello-docker-ggplot2/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(ggplot2) 3 | 4 | ui <- fluidPage( 5 | titlePanel("Hello Docker"), 6 | sidebarLayout( 7 | sidebarPanel( 8 | sliderInput("obs", "Number of observations:", 9 | min = 1, max = 1000, value = 500) 10 | ), 11 | mainPanel( 12 | plotOutput("distPlot") 13 | ) 14 | ) 15 | ) 16 | 17 | server <- function(input, output) { 18 | output$distPlot <- renderPlot({ 19 | ggplot(data.frame(x = rnorm(input$obs))) + aes(x = x) + 20 | geom_histogram() 21 | }) 22 | } 23 | 24 | shinyApp(ui = ui, server = server) 25 | -------------------------------------------------------------------------------- /inst/examples/shiny/r/hello-docker-plain/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | 3 | ui <- fluidPage( 4 | titlePanel("Hello Docker"), 5 | sidebarLayout( 6 | sidebarPanel( 7 | sliderInput("obs", "Number of observations:", 8 | min = 1, max = 1000, value = 500) 9 | ), 10 | mainPanel( 11 | plotOutput("distPlot") 12 | ) 13 | ) 14 | ) 15 | 16 | server <- function(input, output) { 17 | output$distPlot <- renderPlot({ 18 | hist(rnorm(input$obs)) 19 | }) 20 | } 21 | 22 | shinyApp(ui = ui, server = server) 23 | -------------------------------------------------------------------------------- /inst/templates/Dockerfile_Python: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim 2 | 3 | # Install system dependencies 4 | RUN apt-get update && apt-get install -y \ 5 | gcc \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | # Install Shiny for Python 9 | RUN pip install shiny 10 | 11 | # Install additional Python dependencies 12 | {{DEPENDENCIES}} 13 | 14 | # Copy application files 15 | COPY . /app 16 | WORKDIR /app 17 | 18 | # Set environment variables 19 | {{ENV_VARS}} 20 | 21 | # Expose port 22 | EXPOSE {{PORT}} 23 | 24 | # Run the application 25 | CMD ["shiny", "run", "app.py", "--host", "0.0.0.0", "--port", "{{PORT}}"] 26 | 27 | -------------------------------------------------------------------------------- /inst/templates/Dockerfile_R: -------------------------------------------------------------------------------- 1 | FROM rocker/shiny:latest 2 | 3 | # Install system dependencies for devtools 4 | RUN apt-get update && apt-get install -y \ 5 | libcurl4-openssl-dev \ 6 | libssl-dev \ 7 | libxml2-dev \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | # Install R packages 11 | {{DEPENDENCIES}} 12 | 13 | # Copy application files 14 | COPY . /srv/shiny-server/app/ 15 | WORKDIR /srv/shiny-server/app 16 | 17 | # Set environment variables 18 | {{ENV_VARS}} 19 | 20 | # Expose port 21 | EXPOSE {{PORT}} 22 | 23 | # Run the application 24 | CMD ["/usr/bin/shiny-server"] 25 | 26 | -------------------------------------------------------------------------------- /inst/templates/dockerignore: -------------------------------------------------------------------------------- 1 | 2 | # R specific 3 | .RData 4 | .Rhistory 5 | .Rproj.user/ 6 | *.Rproj 7 | renv/library/ 8 | packrat/lib*/ 9 | packrat/src/ 10 | 11 | # Python specific 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | *.so 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | venv/ 30 | ENV/ 31 | .env 32 | .venv 33 | 34 | # Common 35 | .git 36 | .gitignore 37 | .github/ 38 | node_modules/ 39 | Dockerfile 40 | docker-compose.yml 41 | .dockerignore 42 | .DS_Store 43 | *.log 44 | 45 | -------------------------------------------------------------------------------- /man/build_image.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/image.R 3 | \name{build_image} 4 | \alias{build_image} 5 | \title{Build Docker image for a Shiny application} 6 | \usage{ 7 | build_image(app_dir, tag = NULL, build_args = NULL, quiet = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory with Docker configuration.} 11 | 12 | \item{tag}{Character. The tag for the Docker image. If NULL, a tag will be generated 13 | from the directory name.} 14 | 15 | \item{build_args}{Named character vector. Additional build arguments to pass to Docker.} 16 | 17 | \item{quiet}{Logical. If TRUE, suppress Docker build output. Default: FALSE.} 18 | 19 | \item{...}{Additional arguments passed to processx.} 20 | } 21 | \value{ 22 | Invisibly returns the image ID if successful. 23 | } 24 | \description{ 25 | Build Docker image for a Shiny application 26 | } 27 | \examples{ 28 | \dontrun{ 29 | # First create Docker configuration 30 | dockerize("path/to/my/shinyapp") 31 | 32 | # Then build the image 33 | build_image("path/to/my/shinyapp") 34 | 35 | # With a custom tag 36 | build_image("path/to/my/shinyapp", tag = "myorg/myapp:latest") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /man/check_app_directory.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker-core.R 3 | \name{check_app_directory} 4 | \alias{check_app_directory} 5 | \title{Check if a directory exists and is valid} 6 | \usage{ 7 | check_app_directory(app_dir) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to check.} 11 | } 12 | \value{ 13 | Invisibly returns TRUE if valid, otherwise aborts with an error message. 14 | } 15 | \description{ 16 | Check if a directory exists and is valid 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/check_docker_available.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker-core.R 3 | \name{check_docker_available} 4 | \alias{check_docker_available} 5 | \title{Check if Docker is available and abort if not} 6 | \usage{ 7 | check_docker_available() 8 | } 9 | \value{ 10 | Invisibly returns TRUE if Docker is available, otherwise aborts with an error message. 11 | } 12 | \description{ 13 | Check if Docker is available and abort if not 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/check_dockerfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker-core.R 3 | \name{check_dockerfile} 4 | \alias{check_dockerfile} 5 | \title{Check if a Dockerfile exists in the specified directory} 6 | \usage{ 7 | check_dockerfile(app_dir) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the application directory.} 11 | } 12 | \value{ 13 | Invisibly returns TRUE if exists, otherwise aborts with an error message. 14 | } 15 | \description{ 16 | Check if a Dockerfile exists in the specified directory 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/check_shiny_app_files.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/app-detection.R 3 | \name{check_shiny_app_files} 4 | \alias{check_shiny_app_files} 5 | \title{Check if directory contains valid Shiny app files and abort if not} 6 | \usage{ 7 | check_shiny_app_files(app_dir) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory.} 11 | } 12 | \value{ 13 | Invisibly returns TRUE if valid, otherwise aborts with an error message. 14 | } 15 | \description{ 16 | Check if directory contains valid Shiny app files and abort if not 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/cleanup_container.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/export.R 3 | \name{cleanup_container} 4 | \alias{cleanup_container} 5 | \title{Clean up Docker containers and images created by shinydocker} 6 | \usage{ 7 | cleanup_container( 8 | app_dir = NULL, 9 | remove_images = FALSE, 10 | include_running = FALSE, 11 | force = FALSE, 12 | prune = FALSE, 13 | quiet = FALSE, 14 | ... 15 | ) 16 | } 17 | \arguments{ 18 | \item{app_dir}{Character. Path to the Shiny application directory. If provided, 19 | only containers/images related to this application will be removed. 20 | If NULL, all containers/images created by shinydocker will be considered.} 21 | 22 | \item{remove_images}{Logical. If TRUE, also remove Docker images after removing containers. 23 | Default: FALSE.} 24 | 25 | \item{include_running}{Logical. If TRUE, stop and remove running containers. 26 | If FALSE, only stopped containers will be removed. Default: FALSE.} 27 | 28 | \item{force}{Logical. If TRUE, force removal without confirmation. Default: FALSE.} 29 | 30 | \item{prune}{Logical. If TRUE, run docker system prune after cleanup to remove unused data. 31 | Default: FALSE.} 32 | 33 | \item{quiet}{Logical. If TRUE, suppress Docker command output. Default: FALSE.} 34 | 35 | \item{...}{Additional arguments passed to processx.} 36 | } 37 | \value{ 38 | Invisibly returns a list with counts of removed containers and images. 39 | } 40 | \description{ 41 | This function removes Docker containers and optionally images created by the 42 | shinydocker package. It can target a specific application or clean up all 43 | shinydocker-created containers and images. 44 | } 45 | \examples{ 46 | \dontrun{ 47 | # Clean up containers for a specific app 48 | cleanup_container("path/to/my/shinyapp") 49 | 50 | # Clean up all shinydocker containers and images 51 | cleanup_container(remove_images = TRUE) 52 | 53 | # Force cleanup of all containers including running ones 54 | cleanup_container(include_running = TRUE, force = TRUE) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /man/create_docker_compose.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/configuration.R 3 | \name{create_docker_compose} 4 | \alias{create_docker_compose} 5 | \title{Create a docker-compose.yml file for a Shiny application} 6 | \usage{ 7 | create_docker_compose(output_dir, port = 3838, env_vars = NULL, ...) 8 | } 9 | \arguments{ 10 | \item{output_dir}{Character. Directory where the file should be created.} 11 | 12 | \item{port}{Integer. The port to expose.} 13 | 14 | \item{env_vars}{Named character vector. Environment variables.} 15 | 16 | \item{...}{Additional arguments.} 17 | } 18 | \value{ 19 | Character. Path to the created file. 20 | } 21 | \description{ 22 | Create a docker-compose.yml file for a Shiny application 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/create_dockerfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/configuration.R 3 | \name{create_dockerfile} 4 | \alias{create_dockerfile} 5 | \title{Create a Dockerfile for a Shiny application} 6 | \usage{ 7 | create_dockerfile( 8 | output_dir, 9 | app_type, 10 | custom_template = NULL, 11 | port = 3838, 12 | dependencies = list(), 13 | env_vars = NULL, 14 | ... 15 | ) 16 | } 17 | \arguments{ 18 | \item{output_dir}{Character. Directory where the Dockerfile should be created.} 19 | 20 | \item{app_type}{Character. Either "r" or "python".} 21 | 22 | \item{custom_template}{Character. Path to a custom Dockerfile template.} 23 | 24 | \item{port}{Integer. The port to expose.} 25 | 26 | \item{dependencies}{List. Application dependencies.} 27 | 28 | \item{env_vars}{Named character vector. Environment variables.} 29 | 30 | \item{...}{Additional arguments.} 31 | } 32 | \value{ 33 | Character. Path to the created Dockerfile. 34 | } 35 | \description{ 36 | Create a Dockerfile for a Shiny application 37 | } 38 | \keyword{internal} 39 | -------------------------------------------------------------------------------- /man/create_dockerignore.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/configuration.R 3 | \name{create_dockerignore} 4 | \alias{create_dockerignore} 5 | \title{Create a .dockerignore file} 6 | \usage{ 7 | create_dockerignore(output_dir) 8 | } 9 | \arguments{ 10 | \item{output_dir}{Character. Directory where the file should be created.} 11 | } 12 | \value{ 13 | Character. Path to the created file. 14 | } 15 | \description{ 16 | Create a .dockerignore file 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/detect_app_type.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/app-detection.R 3 | \name{detect_app_type} 4 | \alias{detect_app_type} 5 | \title{Detect the type of Shiny application (R or Python)} 6 | \usage{ 7 | detect_app_type(app_dir) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory.} 11 | } 12 | \value{ 13 | Character. Either "r" or "python". 14 | } 15 | \description{ 16 | Detect the type of Shiny application (R or Python) 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/detect_dependencies.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/app-detection.R 3 | \name{detect_dependencies} 4 | \alias{detect_dependencies} 5 | \title{Detect dependencies for a Shiny application} 6 | \usage{ 7 | detect_dependencies(app_dir, app_type) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory.} 11 | 12 | \item{app_type}{Character. Either "r" or "python".} 13 | } 14 | \value{ 15 | Character vector with detected dependencies. 16 | } 17 | \description{ 18 | Detect dependencies for a Shiny application 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/detect_python_dependencies.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/app-detection.R 3 | \name{detect_python_dependencies} 4 | \alias{detect_python_dependencies} 5 | \title{Detect Python package dependencies} 6 | \usage{ 7 | detect_python_dependencies(app_dir) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory.} 11 | } 12 | \value{ 13 | Character vector of detected Python package dependencies. 14 | } 15 | \description{ 16 | Detect Python package dependencies 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/detect_r_dependencies.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/app-detection.R 3 | \name{detect_r_dependencies} 4 | \alias{detect_r_dependencies} 5 | \title{Detect R package dependencies} 6 | \usage{ 7 | detect_r_dependencies(app_dir) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory.} 11 | } 12 | \value{ 13 | Character vector of detected R package dependencies. 14 | } 15 | \description{ 16 | Detect R package dependencies 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/dockerize.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/export.R 3 | \name{dockerize} 4 | \alias{dockerize} 5 | \title{Create Docker configuration for a Shiny application} 6 | \usage{ 7 | dockerize( 8 | app_dir, 9 | output_dir = NULL, 10 | app_type = NULL, 11 | port = 3838, 12 | dependencies = TRUE, 13 | custom_dockerfile = NULL, 14 | env_vars = NULL, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{app_dir}{Character. Path to the Shiny application directory.} 20 | 21 | \item{output_dir}{Character. Path where Docker configuration should be created. 22 | If NULL, files will be created in app_dir.} 23 | 24 | \item{app_type}{Character. Either "r" or "python". If NULL, it will be auto-detected.} 25 | 26 | \item{port}{Integer. The port to expose for the Shiny application. Default: 3838.} 27 | 28 | \item{dependencies}{Logical. Whether to automatically detect and include 29 | dependencies. Default: TRUE.} 30 | 31 | \item{custom_dockerfile}{Character. Path to a custom Dockerfile template to use. 32 | If NULL, the package's built-in templates will be used.} 33 | 34 | \item{env_vars}{Named character vector. Environment variables to include in 35 | the Docker configuration.} 36 | 37 | \item{...}{Additional arguments passed to internal functions.} 38 | } 39 | \value{ 40 | Invisibly returns the path to the created Docker configuration. 41 | } 42 | \description{ 43 | Create Docker configuration for a Shiny application 44 | } 45 | \examples{ 46 | \dontrun{ 47 | # Basic usage with an R Shiny app 48 | dockerize("path/to/my/shinyapp") 49 | 50 | # For a Python Shiny app 51 | dockerize("path/to/my/python/shinyapp", app_type = "python") 52 | 53 | # With custom port and environment variables 54 | dockerize("path/to/my/shinyapp", port = 8080, 55 | env_vars = c(API_KEY = "your-secret-key")) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /man/execute_docker_command.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker-core.R 3 | \name{execute_docker_command} 4 | \alias{execute_docker_command} 5 | \title{Execute a Docker command with standardized error handling} 6 | \usage{ 7 | execute_docker_command( 8 | cmd, 9 | args, 10 | quiet = FALSE, 11 | echo = !quiet, 12 | error_on_status = FALSE, 13 | ... 14 | ) 15 | } 16 | \arguments{ 17 | \item{cmd}{Character. The command to execute (e.g., "docker", "docker-compose").} 18 | 19 | \item{args}{Character vector. Arguments for the command.} 20 | 21 | \item{quiet}{Logical. If TRUE, suppress command output. Default: FALSE.} 22 | 23 | \item{echo}{Logical. Whether to echo command output. Default: !quiet.} 24 | 25 | \item{error_on_status}{Logical. Whether to error on non-zero exit status. Default: FALSE.} 26 | 27 | \item{...}{Additional arguments passed to processx::run.} 28 | } 29 | \value{ 30 | The result of processx::run. 31 | } 32 | \description{ 33 | Execute a Docker command with standardized error handling 34 | } 35 | \keyword{internal} 36 | -------------------------------------------------------------------------------- /man/export.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/export.R 3 | \name{export} 4 | \alias{export} 5 | \title{Export a Shiny app to a Docker container} 6 | \usage{ 7 | export( 8 | app_dir, 9 | output_dir = NULL, 10 | tag = NULL, 11 | port = 3838, 12 | env_vars = NULL, 13 | run = FALSE, 14 | detach = TRUE, 15 | quiet = FALSE, 16 | ... 17 | ) 18 | } 19 | \arguments{ 20 | \item{app_dir}{Character. Path to the Shiny application directory.} 21 | 22 | \item{output_dir}{Character. Path where Docker configuration should be created. 23 | If NULL, files will be created in app_dir.} 24 | 25 | \item{tag}{Character. The tag for the Docker image. If NULL, a tag will be generated 26 | from the directory name.} 27 | 28 | \item{port}{Integer. The port to expose for the Shiny application. Default: 3838.} 29 | 30 | \item{env_vars}{Named character vector. Environment variables to include in 31 | the Docker configuration.} 32 | 33 | \item{run}{Logical. Whether to run the container after building. Default: FALSE.} 34 | 35 | \item{detach}{Logical. If run=TRUE, whether to run in detached mode. Default: TRUE.} 36 | 37 | \item{quiet}{Logical. If TRUE, suppress Docker command output. Default: FALSE.} 38 | 39 | \item{...}{Additional arguments passed to underlying functions.} 40 | } 41 | \value{ 42 | Invisibly returns a list with the paths to the created Docker files and 43 | container information if run=TRUE. 44 | } 45 | \description{ 46 | This function takes a directory containing a Shiny application (either R or Python) 47 | and exports it to an appropriate Docker container. It handles the entire process 48 | including creating Docker configuration, building the Docker image, and optionally 49 | running the container. 50 | } 51 | \examples{ 52 | \dontrun{ 53 | # Basic usage 54 | export("path/to/my/shinyapp") 55 | 56 | # Export and run 57 | export("path/to/my/shinyapp", run = TRUE) 58 | 59 | # Custom configuration 60 | export("path/to/my/shinyapp", 61 | tag = "myorg/myapp:latest", 62 | port = 8080, 63 | env_vars = c(API_KEY = "secret-key"), 64 | run = TRUE) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /man/extract_library_packages.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/app-detection.R 3 | \name{extract_library_packages} 4 | \alias{extract_library_packages} 5 | \title{Extract package names from library() and require() calls} 6 | \usage{ 7 | extract_library_packages(r_code) 8 | } 9 | \arguments{ 10 | \item{r_code}{Character vector. R code to analyze.} 11 | } 12 | \value{ 13 | Character vector of package names. 14 | } 15 | \description{ 16 | Extract package names from library() and require() calls 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/extract_namespace_packages.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/app-detection.R 3 | \name{extract_namespace_packages} 4 | \alias{extract_namespace_packages} 5 | \title{Extract package names from namespace calls (package::function)} 6 | \usage{ 7 | extract_namespace_packages(r_code) 8 | } 9 | \arguments{ 10 | \item{r_code}{Character vector. R code to analyze.} 11 | } 12 | \value{ 13 | Character vector of package names. 14 | } 15 | \description{ 16 | Extract package names from namespace calls (package::function) 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/figures/shinydocker-animated-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | S 150 | 151 | 152 | 153 | 154 | 155 | H 156 | 157 | 158 | 159 | 160 | 161 | I 162 | 163 | 164 | 165 | 166 | 167 | N 168 | 169 | 170 | 171 | 172 | 173 | Y 174 | 175 | 176 | 177 | 178 | 179 | D 180 | 181 | 182 | 183 | 184 | 185 | O 186 | 187 | 188 | 189 | 190 | 191 | C 192 | 193 | 194 | 195 | 196 | 197 | K 198 | 199 | 200 | 201 | 202 | 203 | E 204 | 205 | 206 | 207 | 208 | 209 | R 210 | 211 | 212 | -------------------------------------------------------------------------------- /man/figures/shinydocker-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | S 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | H 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | I 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | N 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | Y 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | D 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | O 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | C 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | K 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | E 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | R 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /man/find_available_port.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{find_available_port} 4 | \alias{find_available_port} 5 | \title{Find an available port starting from a given port} 6 | \usage{ 7 | find_available_port(start_port, max_tries = 100) 8 | } 9 | \arguments{ 10 | \item{start_port}{Integer. The port to start checking from.} 11 | 12 | \item{max_tries}{Integer. Maximum number of ports to check. Default: 100.} 13 | } 14 | \value{ 15 | Integer. An available port or NULL if no port found. 16 | } 17 | \description{ 18 | Find an available port starting from a given port 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/find_containers_for_app.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{find_containers_for_app} 4 | \alias{find_containers_for_app} 5 | \title{Find containers associated with an application} 6 | \usage{ 7 | find_containers_for_app(app_dir, quiet = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the application directory.} 11 | 12 | \item{quiet}{Logical. Whether to suppress messages.} 13 | 14 | \item{...}{Additional arguments passed to processx.} 15 | } 16 | \value{ 17 | Character vector. Container IDs. 18 | } 19 | \description{ 20 | Find containers associated with an application 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/format_dependencies.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/configuration.R 3 | \name{format_dependencies} 4 | \alias{format_dependencies} 5 | \title{Format dependencies for inclusion in Dockerfile} 6 | \usage{ 7 | format_dependencies(app_type, dependencies) 8 | } 9 | \arguments{ 10 | \item{app_type}{Character. Either "r" or "python".} 11 | 12 | \item{dependencies}{List of dependencies.} 13 | } 14 | \value{ 15 | Character string with formatted dependencies. 16 | } 17 | \description{ 18 | Format dependencies for inclusion in Dockerfile 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/format_env_vars.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/configuration.R 3 | \name{format_env_vars} 4 | \alias{format_env_vars} 5 | \title{Format environment variables for inclusion in Dockerfile} 6 | \usage{ 7 | format_env_vars(env_vars) 8 | } 9 | \arguments{ 10 | \item{env_vars}{Named character vector. Environment variables.} 11 | } 12 | \value{ 13 | Character string with formatted environment variables. 14 | } 15 | \description{ 16 | Format environment variables for inclusion in Dockerfile 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/generate_app_url.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{generate_app_url} 4 | \alias{generate_app_url} 5 | \title{Generate app URL based on app type and port} 6 | \usage{ 7 | generate_app_url(app_type, port) 8 | } 9 | \arguments{ 10 | \item{app_type}{Character. Either "r" or "python".} 11 | 12 | \item{port}{Integer. Port number.} 13 | } 14 | \value{ 15 | Character. URL to access the app. 16 | } 17 | \description{ 18 | Generate app URL based on app type and port 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/generate_image_tag.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/image.R 3 | \name{generate_image_tag} 4 | \alias{generate_image_tag} 5 | \title{Generate a default Docker image tag based on app directory} 6 | \usage{ 7 | generate_image_tag(app_dir) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory.} 11 | } 12 | \value{ 13 | Character. Generated tag. 14 | } 15 | \description{ 16 | Generate a default Docker image tag based on app directory 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/get_app_issues.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{get_app_issues} 4 | \alias{get_app_issues} 5 | \title{Get issues and recommendations for Shiny app containerization} 6 | \usage{ 7 | get_app_issues(results) 8 | } 9 | \arguments{ 10 | \item{results}{List. Results from Shiny app checks} 11 | } 12 | \value{ 13 | List with issues and recommendations 14 | } 15 | \description{ 16 | Get issues and recommendations for Shiny app containerization 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/get_docker_issues.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{get_docker_issues} 4 | \alias{get_docker_issues} 5 | \title{Get Docker environment issues and recommendations} 6 | \usage{ 7 | get_docker_issues(results) 8 | } 9 | \arguments{ 10 | \item{results}{List. Results from Docker environment checks} 11 | } 12 | \value{ 13 | List with issues and recommendations 14 | } 15 | \description{ 16 | Get Docker environment issues and recommendations 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/get_platform_flag.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker-core.R 3 | \name{get_platform_flag} 4 | \alias{get_platform_flag} 5 | \title{Get platform flag for Docker if needed} 6 | \usage{ 7 | get_platform_flag(app_type) 8 | } 9 | \arguments{ 10 | \item{app_type}{Character. Either "r" or "python".} 11 | } 12 | \value{ 13 | Character vector. Platform flag for Docker commands or empty vector if not needed. 14 | } 15 | \description{ 16 | Returns platform flag for Docker commands if running on ARM64 and if app type is R. 17 | This is needed because Shiny Server isn't available natively for ARM64 yet. 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/handle_multiple_containers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{handle_multiple_containers} 4 | \alias{handle_multiple_containers} 5 | \title{Handle stopping multiple containers} 6 | \usage{ 7 | handle_multiple_containers(container_ids, quiet = FALSE, force = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{container_ids}{Character vector. Container IDs.} 11 | 12 | \item{quiet}{Logical. Whether to suppress output.} 13 | 14 | \item{force}{Logical. Whether to skip confirmation.} 15 | 16 | \item{...}{Additional arguments passed to processx.} 17 | } 18 | \value{ 19 | Logical. TRUE if successful. 20 | } 21 | \description{ 22 | Handle stopping multiple containers 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/handle_port_configuration.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{handle_port_configuration} 4 | \alias{handle_port_configuration} 5 | \title{Handle port configuration and availability} 6 | \usage{ 7 | handle_port_configuration(port, force_port = FALSE) 8 | } 9 | \arguments{ 10 | \item{port}{Integer. Requested port.} 11 | 12 | \item{force_port}{Logical. Whether to enforce the requested port.} 13 | } 14 | \value{ 15 | Integer. The port to use, either the requested one or an alternative. 16 | } 17 | \description{ 18 | Handle port configuration and availability 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/has_shiny_app_files.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/app-detection.R 3 | \name{has_shiny_app_files} 4 | \alias{has_shiny_app_files} 5 | \title{Check if directory contains Shiny app files} 6 | \usage{ 7 | has_shiny_app_files(app_dir) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory.} 11 | } 12 | \value{ 13 | Logical. TRUE if the directory contains valid Shiny app files. 14 | } 15 | \description{ 16 | Check if directory contains Shiny app files 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/health_dependencies.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_dependencies} 4 | \alias{health_dependencies} 5 | \title{Report dependencies health in a Shiny app} 6 | \usage{ 7 | health_dependencies(app_dir, app_type, verbose = FALSE) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory} 11 | 12 | \item{app_type}{Character. Either "r" or "python"} 13 | 14 | \item{verbose}{Logical. If TRUE, provide more detailed output} 15 | } 16 | \value{ 17 | List with dependency details 18 | } 19 | \description{ 20 | Report dependencies health in a Shiny app 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/health_docker_compose.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_docker_compose} 4 | \alias{health_docker_compose} 5 | \title{Report Docker Compose availability health} 6 | \usage{ 7 | health_docker_compose(timeout = 30, ...) 8 | } 9 | \arguments{ 10 | \item{timeout}{Numeric. Timeout in seconds for commands} 11 | 12 | \item{...}{Additional arguments passed to processx} 13 | } 14 | \value{ 15 | List with Docker Compose details 16 | } 17 | \description{ 18 | Report Docker Compose availability health 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/health_docker_config.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_docker_config} 4 | \alias{health_docker_config} 5 | \title{Report Docker configuration health in a Shiny app directory} 6 | \usage{ 7 | health_docker_config(app_dir, app_type, verbose = FALSE, timeout = 30, ...) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory} 11 | 12 | \item{app_type}{Character. Either "r" or "python"} 13 | 14 | \item{verbose}{Logical. If TRUE, provide more detailed output} 15 | 16 | \item{timeout}{Numeric. Timeout in seconds for commands} 17 | 18 | \item{...}{Additional arguments passed to processx} 19 | } 20 | \value{ 21 | List with Docker configuration details 22 | } 23 | \description{ 24 | Report Docker configuration health in a Shiny app directory 25 | } 26 | \keyword{internal} 27 | -------------------------------------------------------------------------------- /man/health_docker_containers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_docker_containers} 4 | \alias{health_docker_containers} 5 | \title{Report Docker containers health for a Shiny app} 6 | \usage{ 7 | health_docker_containers( 8 | app_dir, 9 | image_name, 10 | verbose = FALSE, 11 | timeout = 30, 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{app_dir}{Character. Path to the Shiny application directory} 17 | 18 | \item{image_name}{Character. Name of the Docker image} 19 | 20 | \item{verbose}{Logical. If TRUE, provide more detailed output} 21 | 22 | \item{timeout}{Numeric. Timeout in seconds for commands} 23 | 24 | \item{...}{Additional arguments passed to processx} 25 | } 26 | \value{ 27 | List with Docker container details 28 | } 29 | \description{ 30 | Report Docker containers health for a Shiny app 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /man/health_docker_daemon.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_docker_daemon} 4 | \alias{health_docker_daemon} 5 | \title{Report Docker daemon health status} 6 | \usage{ 7 | health_docker_daemon(timeout = 30, ...) 8 | } 9 | \arguments{ 10 | \item{timeout}{Numeric. Timeout in seconds for commands} 11 | 12 | \item{...}{Additional arguments passed to processx} 13 | } 14 | \value{ 15 | List with Docker daemon status details 16 | } 17 | \description{ 18 | Report Docker daemon health status 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/health_docker_image.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_docker_image} 4 | \alias{health_docker_image} 5 | \title{Report Docker image health for a Shiny app} 6 | \usage{ 7 | health_docker_image(app_dir, verbose = FALSE, timeout = 30, ...) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory} 11 | 12 | \item{verbose}{Logical. If TRUE, provide more detailed output} 13 | 14 | \item{timeout}{Numeric. Timeout in seconds for commands} 15 | 16 | \item{...}{Additional arguments passed to processx} 17 | } 18 | \value{ 19 | List with Docker image details 20 | } 21 | \description{ 22 | Report Docker image health for a Shiny app 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/health_docker_installation.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_docker_installation} 4 | \alias{health_docker_installation} 5 | \title{Report Docker installation health} 6 | \usage{ 7 | health_docker_installation(timeout = 30, ...) 8 | } 9 | \arguments{ 10 | \item{timeout}{Numeric. Timeout in seconds for commands} 11 | 12 | \item{...}{Additional arguments passed to processx} 13 | } 14 | \value{ 15 | List with Docker installation details 16 | } 17 | \description{ 18 | Report Docker installation health 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/health_docker_permissions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_docker_permissions} 4 | \alias{health_docker_permissions} 5 | \title{Report Docker permissions health} 6 | \usage{ 7 | health_docker_permissions(timeout = 30, ...) 8 | } 9 | \arguments{ 10 | \item{timeout}{Numeric. Timeout in seconds for commands} 11 | 12 | \item{...}{Additional arguments passed to processx} 13 | } 14 | \value{ 15 | List with Docker permission details 16 | } 17 | \description{ 18 | Report Docker permissions health 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/health_docker_resources.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_docker_resources} 4 | \alias{health_docker_resources} 5 | \title{Report Docker resources health} 6 | \usage{ 7 | health_docker_resources(verbose = FALSE, timeout = 30, ...) 8 | } 9 | \arguments{ 10 | \item{verbose}{Logical. If TRUE, provide more detailed output} 11 | 12 | \item{timeout}{Numeric. Timeout in seconds for commands} 13 | 14 | \item{...}{Additional arguments passed to processx} 15 | } 16 | \value{ 17 | List with Docker resource details 18 | } 19 | \description{ 20 | Report Docker resources health 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/health_port_availability.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_port_availability} 4 | \alias{health_port_availability} 5 | \title{Report port availability health} 6 | \usage{ 7 | health_port_availability(ports, timeout = 30, port_timeout = 2, ...) 8 | } 9 | \arguments{ 10 | \item{ports}{Integer vector. Ports to check} 11 | 12 | \item{timeout}{Numeric. Timeout in seconds for commands} 13 | 14 | \item{port_timeout}{Numeric. Timeout in seconds for port checking} 15 | 16 | \item{...}{Additional arguments passed to processx} 17 | } 18 | \value{ 19 | List with port availability details 20 | } 21 | \description{ 22 | Report port availability health 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/health_shiny_app.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_shiny_app} 4 | \alias{health_shiny_app} 5 | \title{Report Shiny app structure health} 6 | \usage{ 7 | health_shiny_app(app_dir, verbose = FALSE) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory} 11 | 12 | \item{verbose}{Logical. If TRUE, provide more detailed output} 13 | } 14 | \value{ 15 | List with Shiny app details 16 | } 17 | \description{ 18 | Report Shiny app structure health 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/health_system_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{health_system_info} 4 | \alias{health_system_info} 5 | \title{Report system information health} 6 | \usage{ 7 | health_system_info() 8 | } 9 | \value{ 10 | List with system information details 11 | } 12 | \description{ 13 | Report system information health 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/is_arm64.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker-core.R 3 | \name{is_arm64} 4 | \alias{is_arm64} 5 | \title{Detect if system is running on ARM64 architecture} 6 | \usage{ 7 | is_arm64() 8 | } 9 | \value{ 10 | Logical. TRUE if the system is running on ARM64 architecture. 11 | } 12 | \description{ 13 | Detect if system is running on ARM64 architecture 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/is_docker_available.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker-core.R 3 | \name{is_docker_available} 4 | \alias{is_docker_available} 5 | \title{Check if Docker is available on the system} 6 | \usage{ 7 | is_docker_available() 8 | } 9 | \value{ 10 | Logical. TRUE if Docker is available in the system path. 11 | } 12 | \description{ 13 | Check if Docker is available on the system 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/is_docker_compose_available.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker-core.R 3 | \name{is_docker_compose_available} 4 | \alias{is_docker_compose_available} 5 | \title{Check if Docker Compose is available} 6 | \usage{ 7 | is_docker_compose_available() 8 | } 9 | \value{ 10 | Logical. TRUE if docker-compose command is available in the system path. 11 | } 12 | \description{ 13 | Check if Docker Compose is available 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/is_port_available.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{is_port_available} 4 | \alias{is_port_available} 5 | \title{Check if a port is available on localhost} 6 | \usage{ 7 | is_port_available(port) 8 | } 9 | \arguments{ 10 | \item{port}{Integer. The port to check.} 11 | } 12 | \value{ 13 | Logical. TRUE if the port is available, FALSE if already in use. 14 | } 15 | \description{ 16 | Check if a port is available on localhost 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/prepare_build_arguments.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/image.R 3 | \name{prepare_build_arguments} 4 | \alias{prepare_build_arguments} 5 | \title{Prepare Docker build arguments} 6 | \usage{ 7 | prepare_build_arguments(app_dir, tag, build_args = NULL, quiet = FALSE) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to application directory.} 11 | 12 | \item{tag}{Character. Image tag.} 13 | 14 | \item{build_args}{Named character vector. Additional build arguments.} 15 | 16 | \item{quiet}{Logical. Whether to use quiet mode.} 17 | } 18 | \value{ 19 | Character vector of build arguments. 20 | } 21 | \description{ 22 | Prepare Docker build arguments 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/process_containers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/export.R 3 | \name{process_containers} 4 | \alias{process_containers} 5 | \title{Process and remove containers} 6 | \usage{ 7 | process_containers(container_lines, include_running, force, quiet, ...) 8 | } 9 | \arguments{ 10 | \item{container_lines}{Character vector. Container data lines.} 11 | 12 | \item{include_running}{Logical. Whether to include running containers.} 13 | 14 | \item{force}{Logical. Whether to force removal.} 15 | 16 | \item{quiet}{Logical. Whether to suppress output.} 17 | 18 | \item{...}{Additional arguments passed to processx.} 19 | } 20 | \value{ 21 | Integer. Number of removed containers. 22 | } 23 | \description{ 24 | Process and remove containers 25 | } 26 | \keyword{internal} 27 | -------------------------------------------------------------------------------- /man/process_dockerfile_template.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/configuration.R 3 | \name{process_dockerfile_template} 4 | \alias{process_dockerfile_template} 5 | \title{Process a Dockerfile template by replacing placeholders} 6 | \usage{ 7 | process_dockerfile_template( 8 | template_content, 9 | app_type, 10 | port, 11 | dependencies, 12 | env_vars 13 | ) 14 | } 15 | \arguments{ 16 | \item{template_content}{Character vector. Template content.} 17 | 18 | \item{app_type}{Character. Either "r" or "python".} 19 | 20 | \item{port}{Integer. The port to expose.} 21 | 22 | \item{dependencies}{List. Application dependencies.} 23 | 24 | \item{env_vars}{Named character vector. Environment variables.} 25 | } 26 | \value{ 27 | Character vector with processed template. 28 | } 29 | \description{ 30 | Process a Dockerfile template by replacing placeholders 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /man/remove_shinydocker_images.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/export.R 3 | \name{remove_shinydocker_images} 4 | \alias{remove_shinydocker_images} 5 | \title{Remove shinydocker images} 6 | \usage{ 7 | remove_shinydocker_images(app_dir, force, quiet, ...) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the application directory. If NULL, all shinydocker images.} 11 | 12 | \item{force}{Logical. Whether to force removal.} 13 | 14 | \item{quiet}{Logical. Whether to suppress output.} 15 | 16 | \item{...}{Additional arguments passed to processx.} 17 | } 18 | \value{ 19 | Integer. Number of removed images. 20 | } 21 | \description{ 22 | Remove shinydocker images 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/run_container.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{run_container} 4 | \alias{run_container} 5 | \title{Run a Docker container with a Shiny application} 6 | \usage{ 7 | run_container( 8 | app_dir, 9 | tag = NULL, 10 | port = 3838, 11 | detach = FALSE, 12 | env_vars = NULL, 13 | docker_compose = TRUE, 14 | quiet = FALSE, 15 | force_port = FALSE, 16 | ... 17 | ) 18 | } 19 | \arguments{ 20 | \item{app_dir}{Character. Path to the Shiny application directory with Docker configuration.} 21 | 22 | \item{tag}{Character. The tag for the Docker image to run. If NULL, a tag will be generated 23 | from the directory name (should match what was used in build_image).} 24 | 25 | \item{port}{Integer. The port to map to the container's exposed port. Default: 3838.} 26 | 27 | \item{detach}{Logical. If TRUE, run container in detached mode. Default: FALSE.} 28 | 29 | \item{env_vars}{Named character vector. Environment variables to pass to the container.} 30 | 31 | \item{docker_compose}{Logical. If TRUE, use docker-compose for running if available. Default: TRUE.} 32 | 33 | \item{quiet}{Logical. If TRUE, suppress Docker command output. Default: FALSE.} 34 | 35 | \item{force_port}{Logical. If TRUE, fail if requested port is unavailable. If FALSE, try to find an alternative port. Default: FALSE.} 36 | 37 | \item{...}{Additional arguments passed to processx.} 38 | } 39 | \value{ 40 | For detached mode, invisibly returns the container ID if successful. 41 | For attached mode, the function will block until the container stops. 42 | } 43 | \description{ 44 | Run a Docker container with a Shiny application 45 | } 46 | \examples{ 47 | \dontrun{ 48 | # First create Docker configuration and build 49 | dockerize("path/to/my/shinyapp") 50 | build_image("path/to/my/shinyapp") 51 | 52 | # Run the container 53 | run_container("path/to/my/shinyapp") 54 | 55 | # Run detached on a different port 56 | run_container("path/to/my/shinyapp", port = 8080, detach = TRUE) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /man/run_docker_prune.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/export.R 3 | \name{run_docker_prune} 4 | \alias{run_docker_prune} 5 | \title{Run docker system prune} 6 | \usage{ 7 | run_docker_prune(force, quiet, ...) 8 | } 9 | \arguments{ 10 | \item{force}{Logical. Whether to force prune.} 11 | 12 | \item{quiet}{Logical. Whether to suppress output.} 13 | 14 | \item{...}{Additional arguments passed to processx.} 15 | } 16 | \value{ 17 | Invisibly returns TRUE. 18 | } 19 | \description{ 20 | Run docker system prune 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/run_with_compose.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{run_with_compose} 4 | \alias{run_with_compose} 5 | \title{Run container using docker-compose} 6 | \usage{ 7 | run_with_compose( 8 | app_dir, 9 | port, 10 | env_vars = NULL, 11 | detach = FALSE, 12 | quiet = FALSE, 13 | ... 14 | ) 15 | } 16 | \arguments{ 17 | \item{app_dir}{Character. Path to the Shiny application directory.} 18 | 19 | \item{port}{Integer. The port to map.} 20 | 21 | \item{env_vars}{Named character vector. Environment variables.} 22 | 23 | \item{detach}{Logical. Whether to run in detached mode.} 24 | 25 | \item{quiet}{Logical. Whether to suppress output.} 26 | 27 | \item{...}{Additional arguments passed to processx.} 28 | } 29 | \value{ 30 | Character. Container ID if successful. 31 | } 32 | \description{ 33 | Run container using docker-compose 34 | } 35 | \keyword{internal} 36 | -------------------------------------------------------------------------------- /man/run_with_docker.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{run_with_docker} 4 | \alias{run_with_docker} 5 | \title{Run container using docker run} 6 | \usage{ 7 | run_with_docker( 8 | tag, 9 | port, 10 | app_type, 11 | env_vars = NULL, 12 | detach = FALSE, 13 | quiet = FALSE, 14 | ... 15 | ) 16 | } 17 | \arguments{ 18 | \item{tag}{Character. The Docker image tag.} 19 | 20 | \item{port}{Integer. The port to map.} 21 | 22 | \item{app_type}{Character. Either "r" or "python".} 23 | 24 | \item{env_vars}{Named character vector. Environment variables.} 25 | 26 | \item{detach}{Logical. Whether to run in detached mode.} 27 | 28 | \item{quiet}{Logical. Whether to suppress output.} 29 | 30 | \item{...}{Additional arguments passed to processx.} 31 | } 32 | \value{ 33 | Character. Container ID if successful. 34 | } 35 | \description{ 36 | Run container using docker run 37 | } 38 | \keyword{internal} 39 | -------------------------------------------------------------------------------- /man/should_use_compose.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{should_use_compose} 4 | \alias{should_use_compose} 5 | \title{Determine if docker-compose should be used for container operations} 6 | \usage{ 7 | should_use_compose(app_dir, docker_compose = TRUE) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the Shiny application directory.} 11 | 12 | \item{docker_compose}{Logical. User preference for using docker-compose.} 13 | } 14 | \value{ 15 | Logical. TRUE if docker-compose should be used. 16 | } 17 | \description{ 18 | Determine if docker-compose should be used for container operations 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/sitrep_app_conversion.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{sitrep_app_conversion} 4 | \alias{sitrep_app_conversion} 5 | \title{Situation report for Shiny app containerization} 6 | \usage{ 7 | sitrep_app_conversion( 8 | app_dir, 9 | check_dependencies = TRUE, 10 | verbose = FALSE, 11 | timeout = 30, 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{app_dir}{Character. Path to the Shiny application directory to analyze.} 17 | 18 | \item{check_dependencies}{Logical. If TRUE, detect and display app dependencies. Default: TRUE.} 19 | 20 | \item{verbose}{Logical. If TRUE, provide more detailed output. Default: FALSE.} 21 | 22 | \item{timeout}{Numeric. Timeout in seconds for commands. Default: 30.} 23 | 24 | \item{...}{Additional arguments passed to processx.} 25 | } 26 | \value{ 27 | Invisibly returns a list with Shiny app containerization diagnostic results. 28 | } 29 | \description{ 30 | This function analyzes a Shiny application directory and checks its readiness 31 | for containerization. It examines Shiny app structure, Docker configuration files, 32 | dependency detection, image building status, and running containers. 33 | } 34 | \examples{ 35 | \dontrun{ 36 | # Basic Shiny app containerization report 37 | sitrep_app_conversion("path/to/my/shinyapp") 38 | 39 | # Detailed report with dependency analysis 40 | sitrep_app_conversion("path/to/my/shinyapp", verbose = TRUE) 41 | 42 | # Skip dependency detection 43 | sitrep_app_conversion("path/to/my/shinyapp", check_dependencies = FALSE) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /man/sitrep_docker.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sitrep.R 3 | \name{sitrep_docker} 4 | \alias{sitrep_docker} 5 | \title{Situation report for Docker environment} 6 | \usage{ 7 | sitrep_docker( 8 | check_ports = TRUE, 9 | verbose = FALSE, 10 | timeout = 30, 11 | port_timeout = 2, 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{check_ports}{Logical. If TRUE, check common port conflicts. Default: TRUE.} 17 | 18 | \item{verbose}{Logical. If TRUE, provide more detailed output. Default: FALSE.} 19 | 20 | \item{timeout}{Numeric. Timeout in seconds for Docker commands. Default: 30.} 21 | 22 | \item{port_timeout}{Numeric. Timeout in seconds for port checking. Default: 2.} 23 | 24 | \item{...}{Additional arguments passed to processx.} 25 | } 26 | \value{ 27 | Invisibly returns a list with Docker environment diagnostic results. 28 | } 29 | \description{ 30 | This function provides a comprehensive report of the Docker environment including 31 | installation status, version information, daemon status, permissions, resource usage, 32 | and port availability. It helps identify issues with Docker setup that might affect 33 | containerizing Shiny applications. 34 | } 35 | \examples{ 36 | \dontrun{ 37 | # Basic Docker environment report 38 | sitrep_docker() 39 | 40 | # Detailed Docker environment report 41 | sitrep_docker(verbose = TRUE) 42 | 43 | # Skip port checking 44 | sitrep_docker(check_ports = FALSE) 45 | 46 | # Use shorter timeout for port checking 47 | sitrep_docker(port_timeout = 1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /man/stop_all_containers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{stop_all_containers} 4 | \alias{stop_all_containers} 5 | \title{Stop all running Docker containers} 6 | \usage{ 7 | stop_all_containers(quiet = FALSE, force = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{quiet}{Logical. Whether to suppress output.} 11 | 12 | \item{force}{Logical. Whether to skip confirmation.} 13 | 14 | \item{...}{Additional arguments passed to processx.} 15 | } 16 | \value{ 17 | Logical. TRUE if successful. 18 | } 19 | \description{ 20 | Stop all running Docker containers 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/stop_container.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{stop_container} 4 | \alias{stop_container} 5 | \title{Stop running Docker containers for a Shiny application} 6 | \usage{ 7 | stop_container( 8 | app_dir = NULL, 9 | container_id = NULL, 10 | docker_compose = TRUE, 11 | quiet = FALSE, 12 | force = FALSE, 13 | ... 14 | ) 15 | } 16 | \arguments{ 17 | \item{app_dir}{Character. Path to the Shiny application directory with Docker configuration. 18 | If provided, the function will attempt to find and stop containers based on the image name.} 19 | 20 | \item{container_id}{Character. ID or name of the container to stop. If NULL, 21 | the function will try to find containers based on app_dir.} 22 | 23 | \item{docker_compose}{Logical. If TRUE, use docker-compose for stopping if available. Default: TRUE.} 24 | 25 | \item{quiet}{Logical. If TRUE, suppress Docker command output. Default: FALSE.} 26 | 27 | \item{force}{Logical. If TRUE and stopping all containers, skip confirmation prompt. Default: FALSE.} 28 | 29 | \item{...}{Additional arguments passed to processx.} 30 | } 31 | \value{ 32 | Invisibly returns TRUE if successful, FALSE otherwise. 33 | } 34 | \description{ 35 | This function stops a running Docker container that was started with 36 | \code{run_container()} or \code{export()}. It can stop containers by ID 37 | or by finding containers associated with the specified application directory. 38 | If neither app_dir nor container_id is provided, it will stop all running containers. 39 | } 40 | \examples{ 41 | \dontrun{ 42 | # First run a container 43 | result <- export("path/to/my/shinyapp", run = TRUE) 44 | 45 | # Stop by container ID 46 | stop_container(container_id = result$container_id) 47 | 48 | # Or stop by app directory 49 | stop_container("path/to/my/shinyapp") 50 | 51 | # Stop all running containers 52 | stop_container() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /man/stop_containers_for_app.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{stop_containers_for_app} 4 | \alias{stop_containers_for_app} 5 | \title{Stop containers for a specific application} 6 | \usage{ 7 | stop_containers_for_app(app_dir, quiet = FALSE, force = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the application directory.} 11 | 12 | \item{quiet}{Logical. Whether to suppress output.} 13 | 14 | \item{force}{Logical. Whether to skip confirmation.} 15 | 16 | \item{...}{Additional arguments passed to processx.} 17 | } 18 | \value{ 19 | Logical. TRUE if successful. 20 | } 21 | \description{ 22 | Stop containers for a specific application 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/stop_docker_container.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{stop_docker_container} 4 | \alias{stop_docker_container} 5 | \title{Stop a Docker container} 6 | \usage{ 7 | stop_docker_container(container_id, quiet = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{container_id}{Character. ID of the container to stop.} 11 | 12 | \item{quiet}{Logical. Whether to suppress output.} 13 | 14 | \item{...}{Additional arguments passed to processx.} 15 | } 16 | \value{ 17 | Logical. TRUE if successful. 18 | } 19 | \description{ 20 | Stop a Docker container 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/stop_running_containers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/export.R 3 | \name{stop_running_containers} 4 | \alias{stop_running_containers} 5 | \title{Stop running containers} 6 | \usage{ 7 | stop_running_containers(container_ids, quiet, ...) 8 | } 9 | \arguments{ 10 | \item{container_ids}{Character vector. Container IDs to stop.} 11 | 12 | \item{quiet}{Logical. Whether to suppress output.} 13 | 14 | \item{...}{Additional arguments passed to processx.} 15 | } 16 | \value{ 17 | Invisibly returns TRUE. 18 | } 19 | \description{ 20 | Stop running containers 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/stop_with_compose.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{stop_with_compose} 4 | \alias{stop_with_compose} 5 | \title{Stop containers using docker-compose} 6 | \usage{ 7 | stop_with_compose(app_dir, quiet = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the application directory.} 11 | 12 | \item{quiet}{Logical. Whether to suppress output.} 13 | 14 | \item{...}{Additional arguments passed to processx.} 15 | } 16 | \value{ 17 | Logical. TRUE if successful. 18 | } 19 | \description{ 20 | Stop containers using docker-compose 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/update_compose_port.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/container.R 3 | \name{update_compose_port} 4 | \alias{update_compose_port} 5 | \title{Update port mapping in docker-compose.yml} 6 | \usage{ 7 | update_compose_port(app_dir, port) 8 | } 9 | \arguments{ 10 | \item{app_dir}{Character. Path to the application directory.} 11 | 12 | \item{port}{Integer. New port to use.} 13 | } 14 | \value{ 15 | Invisibly returns TRUE if successful. 16 | } 17 | \description{ 18 | Update port mapping in docker-compose.yml 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /shinydocker.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: ae08b067-0a62-43fc-a628-454dcf14140f 3 | 4 | RestoreWorkspace: Default 5 | SaveWorkspace: Default 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | --------------------------------------------------------------------------------