R/docker_generation.R

Defines functions .generate_docker_readme .generate_dockerignore .generate_docker_compose .generate_dockerfile generate_docker

Documented in generate_docker .generate_docker_compose .generate_dockerfile .generate_dockerignore .generate_docker_readme

#' Generate Docker Configuration
#'
#' @description
#' Generate a Dockerfile and docker-compose.yml for complete environment reproducibility
#'
#' @param output_dir Character. Directory to save Docker files (required).
#' @param r_version Character. R version to use. Default is current R version.
#' @param base_image Character. Base Docker image. Default "rocker/r-ver"
#' @param system_deps Character vector. System dependencies to install
#' @param project_name Character. Name for the project
#' @param include_rstudio Logical. Include RStudio Server. Default FALSE.
#'
#' @return List of generated file paths
#'
#' @export
#'
#' @examples
#' \dontrun{
#' generate_docker(
#'   output_dir = tempdir(),
#'   project_name = "my_analysis",
#'   system_deps = c("libcurl4-openssl-dev", "libxml2-dev")
#' )
#' }
generate_docker <- function(output_dir,
                            r_version = NULL,
                            base_image = "rocker/r-ver",
                            system_deps = NULL,
                            project_name = "reproflow-project",
                            include_rstudio = FALSE) {
  if (is.null(r_version)) {
    r_version <- paste(R.version$major, R.version$minor, sep = ".")
  }

  # Override base image if RStudio requested
  if (include_rstudio) {
    base_image <- "rocker/rstudio"
  }

  # Create output directory if it doesn't exist
  dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)

  # Generate Dockerfile
  dockerfile_path <- file.path(output_dir, "Dockerfile")
  .generate_dockerfile(
    dockerfile_path,
    r_version,
    base_image,
    system_deps
  )

  # Generate docker-compose.yml
  compose_path <- file.path(output_dir, "docker-compose.yml")
  .generate_docker_compose(
    compose_path,
    project_name,
    include_rstudio
  )

  # Generate .dockerignore
  dockerignore_path <- file.path(output_dir, ".dockerignore")
  .generate_dockerignore(dockerignore_path)

  # Generate README for Docker usage
  docker_readme_path <- file.path(output_dir, "DOCKER_README.md")
  .generate_docker_readme(docker_readme_path, project_name, include_rstudio)

  cli::cli_alert_success("Docker configuration generated in {.file {output_dir}}")
  cli::cli_ul(c(
    "Dockerfile",
    "docker-compose.yml",
    ".dockerignore",
    "DOCKER_README.md"
  ))

  invisible(list(
    dockerfile = dockerfile_path,
    compose = compose_path,
    dockerignore = dockerignore_path,
    readme = docker_readme_path
  ))
}


#' Generate Dockerfile
#'
#' @description
#' Internal function to generate Dockerfile content
#'
#' @keywords internal
.generate_dockerfile <- function(output_file, r_version, base_image, system_deps) {
  dockerfile_lines <- c(
    paste0("# Reproducible R Environment"),
    paste0("# Generated by Capsule on ", Sys.time()),
    paste0("FROM ", base_image, ":", r_version),
    "",
    "# Set working directory",
    "WORKDIR /project",
    "",
    "# Install system dependencies",
    "RUN apt-get update && apt-get install -y \\"
  )

  # Add system dependencies
  default_deps <- c(
    "libcurl4-openssl-dev",
    "libssl-dev",
    "libxml2-dev",
    "libfontconfig1-dev",
    "libharfbuzz-dev",
    "libfribidi-dev",
    "libfreetype6-dev",
    "libpng-dev",
    "libtiff5-dev",
    "libjpeg-dev",
    "git"
  )

  all_deps <- unique(c(default_deps, system_deps))

  for (i in seq_along(all_deps)) {
    end_char <- if (i < length(all_deps)) " \\" else ""
    dockerfile_lines <- c(
      dockerfile_lines,
      paste0("    ", all_deps[i], end_char)
    )
  }

  dockerfile_lines <- c(
    dockerfile_lines,
    "    && rm -rf /var/lib/apt/lists/*",
    "",
    "# Install renv for package management",
    "RUN R -e \"install.packages('renv', repos='https://cloud.r-project.org/')\"",
    "",
    "# Copy project files",
    "COPY . /project/",
    "",
    "# Restore R packages from renv lockfile",
    "RUN R -e \"if (file.exists('renv.lock')) renv::restore()\"",
    "",
    "# Default command",
    "CMD [\"R\"]"
  )

  writeLines(dockerfile_lines, output_file)
  cli::cli_alert_success("Dockerfile created: {.file {output_file}}")
}


#' Generate docker-compose.yml
#'
#' @description
#' Internal function to generate docker-compose configuration
#'
#' @keywords internal
.generate_docker_compose <- function(output_file, project_name, include_rstudio) {
  compose_lines <- c(
    "version: '3.8'",
    "",
    "services:",
    paste0("  ", project_name, ":"),
    "    build: .",
    "    volumes:",
    "      - .:/project",
    "      - renv-cache:/renv/cache",
    "    environment:",
    "      - RENV_PATHS_CACHE=/renv/cache"
  )

  if (include_rstudio) {
    compose_lines <- c(
      compose_lines,
      "      - PASSWORD=rstudio",
      "    ports:",
      "      - \"8787:8787\""
    )
  }

  compose_lines <- c(
    compose_lines,
    "",
    "volumes:",
    "  renv-cache:"
  )

  writeLines(compose_lines, output_file)
  cli::cli_alert_success("docker-compose.yml created: {.file {output_file}}")
}


#' Generate .dockerignore
#'
#' @description
#' Internal function to generate .dockerignore file
#'
#' @keywords internal
.generate_dockerignore <- function(output_file) {
  dockerignore_lines <- c(
    "# Git",
    ".git",
    ".gitignore",
    "",
    "# R",
    ".Rproj.user",
    ".Rhistory",
    ".RData",
    ".Ruserdata",
    "",
    "# renv",
    "renv/library",
    "renv/local",
    "renv/cellar",
    "renv/lock",
    "renv/python",
    "renv/sandbox",
    "renv/staging",
    "",
    "# Docker",
    "Dockerfile",
    "docker-compose.yml",
    ".dockerignore",
    "",
    "# Misc",
    "*.Rproj",
    ".DS_Store",
    "Thumbs.db"
  )

  writeLines(dockerignore_lines, output_file)
  cli::cli_alert_success(".dockerignore created: {.file {output_file}}")
}


#' Generate Docker README
#'
#' @description
#' Internal function to generate Docker usage instructions
#'
#' @keywords internal
.generate_docker_readme <- function(output_file, project_name, include_rstudio) {
  readme_lines <- c(
    "# Docker Setup for Reproducible R Environment",
    "",
    "This directory contains Docker configuration for running your R analysis in a completely reproducible environment.",
    "",
    "## Prerequisites",
    "",
    "- Docker installed on your system",
    "- Docker Compose (usually comes with Docker Desktop)",
    "",
    "## Quick Start",
    "",
    "### Build the Docker image",
    "",
    "```bash",
    "docker-compose build",
    "```",
    "",
    "### Run the container",
    ""
  )

  if (include_rstudio) {
    readme_lines <- c(
      readme_lines,
      "```bash",
      "docker-compose up",
      "```",
      "",
      "Then open your browser to http://localhost:8787",
      "",
      "- Username: rstudio",
      "- Password: rstudio",
      ""
    )
  } else {
    readme_lines <- c(
      readme_lines,
      "```bash",
      "docker-compose run --rm ", project_name, " R",
      "```",
      ""
    )
  }

  readme_lines <- c(
    readme_lines,
    "### Run your analysis script",
    "",
    "```bash",
    paste0("docker-compose run --rm ", project_name, " Rscript your_script.R"),
    "```",
    "",
    "## Container Details",
    "",
    paste0("- **Service name**: ", project_name),
    paste0("- **R Version**: ", paste(R.version$major, R.version$minor, sep = ".")),
    "- **Package management**: renv",
    "",
    "## Volume Mounts",
    "",
    "- Current directory is mounted to `/project` in the container",
    "- renv cache is persisted in a named volume for faster rebuilds",
    "",
    "## Customization",
    "",
    "### Adding System Dependencies",
    "",
    "Edit the `Dockerfile` and add packages to the `apt-get install` command.",
    "",
    "### Changing R Version",
    "",
    "Modify the version tag in the `FROM` line of the Dockerfile.",
    "",
    "## Cleaning Up",
    "",
    "```bash",
    "# Stop containers",
    "docker-compose down",
    "",
    "# Remove volumes (including renv cache)",
    "docker-compose down -v",
    "```",
    "",
    "## Troubleshooting",
    "",
    "### Packages not installing",
    "",
    "Try rebuilding without cache:",
    "",
    "```bash",
    "docker-compose build --no-cache",
    "```",
    "",
    "### Permission issues",
    "",
    "The container runs as root by default. Generated files will be owned by root.",
    "Add this to your docker-compose.yml under the service:",
    "",
    "```yaml",
    paste0("user: \"", Sys.info()["uid"], ":", Sys.info()["gid"], "\""),
    "```"
  )

  writeLines(readme_lines, output_file)
  cli::cli_alert_success("Docker README created: {.file {output_file}}")
}

Try the Capsule package in your browser

Any scripts or data that you put into this service are public.

Capsule documentation built on Nov. 11, 2025, 5:14 p.m.