├── .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
16 |
17 |
18 | [](https://github.com/coatless-rpkg/shinydocker/actions/workflows/R-CMD-check.yaml)
19 | 
20 | 
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
5 |
6 |
7 |
8 | [](https://github.com/coatless-rpkg/shinydocker/actions/workflows/R-CMD-check.yaml)
9 | 
10 | 
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 |
--------------------------------------------------------------------------------
/man/figures/shinydocker-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------