Tempfile and Tmpdir for Temporary Storage
Ruby’s Tempfile class and tmpdir utilities are the standard-library answers to temporary storage. When you need a Tempfile for buffering large datasets, handling file uploads, or creating scratch workspaces for batch operations, these helpers take care of the tedious parts like picking unique paths and remembering to delete the file afterward. This guide covers Tempfile, Dir.tmpdir, and Dir.mktmpdir, the three main utilities for temporary storage.
Intro context
Temporary storage sits in the middle of a lot of real Ruby workflows. You might need it when you receive an upload, render a report, decompress an archive, or write a scratch file before moving the result somewhere permanent. The goal is not to keep the data forever, but to keep the working copy isolated, secure, and easy to clean up.
That is why these helpers matter more than they first appear to. They remove the tedious parts of temporary-file handling, such as picking a unique path, setting safe permissions, and remembering to delete the file later. If you already know Ruby file I/O, tempfiles fit naturally into the same mental model, except with stronger cleanup rules.
Why use temporary files?
You need temporary storage when working with data that shouldn’t persist permanently. Common scenarios include:
- Processing files too large to fit in memory
- Storing intermediate results during multi-step transformations
- Handling uploaded files before moving them to permanent storage
- Creating scratch workspaces for parallel operations
Ruby’s temporary file utilities solve several problems automatically: they generate unique filenames to prevent collisions, create files with restricted permissions for security, and support automatic cleanup so you don’t leave behind orphan files.
Those details matter in production code because temporary paths are often shared by multiple processes. A good temporary-file API needs to avoid collisions, avoid leaks, and avoid leaving behind half-finished files when something goes wrong. The stdlib helpers cover those concerns so you can focus on the real work that happens inside the block.
Creating temporary files with Tempfile
The Tempfile class from Ruby’s standard library creates temporary files with predictable behavior. Two main methods exist: Tempfile.create (recommended) and Tempfile.new (legacy).
When you pick between them, think about lifetime and cleanup first. If the file only needs to exist for a short block of work, the block form is the best fit. If you need to pass the file object to other code, you can still manage it manually, but then you should be explicit about closing and deleting it.
Tempfile.create (recommended)
Tempfile.create is the modern approach. It returns a regular File object rather than a Tempfile object, which means better performance and cleaner semantics:
require 'tempfile'
Tempfile.create('myapp') do |file|
file.puts "Hello, World!"
file.rewind
puts file.read
end
# => Hello, World!
# File is automatically deleted after the block
The block form guarantees cleanup, so the file is deleted when the block exits even if an exception occurs. Without a block, you’re responsible for cleanup:
That guarantee is the main reason Tempfile.create is the default recommendation. It gives you a file-like object without making you remember extra cleanup code, which makes failures safer and the happy path easier to read. The block is a scope that owns the file’s lifetime, so you never have to pair a create call with a matching delete call somewhere else in the code. That makes the resource management as obvious as the indentation around the block.
If you cannot use a block because the file needs to outlive a single method call, the manual form below still works. When you take this route, pair every create with an explicit close and unlink so the file does not outlive the process. An ensure clause is the standard way to make that cleanup reliable even when the code between creation and deletion raises an exception.
file = Tempfile.create('data')
file.write "Some content"
file.close
File.unlink(file.path)
Security: file permissions
Tempfile creates files with restrictive permissions (0600) by default, meaning only the owner can read or write:
require 'tempfile'
file = Tempfile.create('secure')
puts file.stat.mode.to_s(8)
# => 100600 (owner read/write only)
This is important when handling sensitive data. The 0600 permission ensures other users on the system cannot read your temporary files.
If your application handles uploaded documents, authentication tokens, or other private material, this default is not just a nice extra. It is part of the safety story, because a temporary file that is visible to the wrong user can become a security problem very quickly. The 0600 permission means only the owner process can read or write the file, which eliminates a whole class of information-leak bugs before they appear. When you need an even stronger guarantee that the file content never appears in the filesystem at all, Ruby provides the anonymous option described next. It uses kernel-level support on Linux to keep the data accessible only through the open file descriptor.
The anonymous option
For highly sensitive data that should never be visible to other processes, use anonymous: true:
require 'tempfile'
Tempfile.create(anonymous: true) do |file|
file.write "secret data"
file.rewind
puts file.read
end
# => secret data
# File is unlinked immediately after creation - never visible on filesystem
On Linux, this uses O_TMPFILE for efficient kernel-level support. The file exists only as an open file descriptor.
Anonymous tempfiles are best when the data should never appear in the filesystem namespace at all. That is a narrower use case, but it is useful for workloads that care about privacy or need to avoid races around a discoverable path.
Dir.tmpdir: finding the system temp directory
Dir.tmpdir returns the path to the system’s temporary directory. It checks multiple environment variables and falls back to platform-specific defaults:
require 'tmpdir'
puts Dir.tmpdir
# => "/tmp" on most Linux systems
# => "C:\\Users\\Username\\AppData\\Local\\Temp" on Windows
The search order is: TMPDIR, TMP, TEMP, then platform-specific defaults (/tmp on Unix, user temp folder on Windows), and finally the current directory as a last resort.
That fallback order helps keep the method useful across different environments, but it also explains why your program should not assume the temp path is always the same. If you need a specific location, read the value at runtime instead of hard-coding a path that only works on your machine.
You can use this to create temp files in a specific location:
This is handy when you want all scratch files for a task to live in the same place, such as a job-specific temp folder or a mount point with different storage characteristics.
require 'tempfile'
file = Tempfile.new('custom', Dir.tmpdir)
puts file.path
# => "/tmp/custom20260318-12345-67890"
Creating temporary directories with Dir.mktmpdir
When you need a temporary directory rather than a file, Dir.mktmpdir creates one with secure permissions (0700):
Directories are the right choice when the work itself creates more than one file, or when a library expects to operate inside a folder structure. Using a temp directory keeps those files grouped together and makes cleanup much simpler.
require 'tmpdir'
Dir.mktmpdir do |dir|
puts "Created: #{dir}"
# => Created: "/tmp/20260318-12345-67890"
File.write("#{dir}/data.txt", "temporary content")
puts File.read("#{dir}/data.txt")
# => temporary content
end
# Directory is automatically removed after block exits
The block form ensures the directory is cleaned up. Without a block, you must handle cleanup yourself:
The same cleanup rule applies here as it does for tempfiles. If you can keep the work inside a block, do that first. If you cannot, then make sure cleanup runs in an ensure clause so the directory is removed even when an error interrupts the work. A temporary directory is a container that holds several scratch files at once, so the cleanup cost is higher when it fails. The ensure block in the manual example runs regardless of whether the code inside the begin block raises an exception, which means the directory is always removed. Using FileUtils.remove_entry instead of a plain Dir.rmdir handles the case where the directory still contains files, avoiding a cleanup error on non-empty directories.
require 'tmpdir'
require 'fileutils'
dir = Dir.mktmpdir('myapp')
begin
File.write("#{dir}/output.txt", "results")
ensure
FileUtils.remove_entry dir
end
You can specify prefix and suffix for the directory name:
Dir.mktmpdir(['session_', '.cache']) do |dir|
puts dir
# => "/tmp/session_20260318-xxxxxx.cache"
end
That pattern is useful when you want a directory name that is still recognizable in logs or debugging output. A small prefix can make it much easier to spot which temporary workspace belongs to which part of the application.
Understanding cleanup behavior
Block forms (recommended)
Both Tempfile.create and Dir.mktmpdir support block forms that guarantee cleanup:
Tempfile.create do |file|
# File exists here
end
# File is deleted here, guaranteed
The block form is safest because cleanup happens when the block exits, which gives you predictable timing and no reliance on garbage collection.
That predictability is one of the biggest practical wins here. A block makes the lifetime of the temporary resource obvious from the surrounding code, which means future readers do not have to guess when the file or directory disappears. The block form also handles the case where an exception is raised inside the block because Ruby still runs the cleanup code when unwinding the stack. That means you get delete-on-error behavior without writing a separate rescue or ensure clause yourself. For these reasons, Tempfile.create with a block should be your first choice in almost every situation where the file does not need to outlive the block.
Manual cleanup with Tempfile.new
Tempfile.new and Tempfile.open are older methods that return Tempfile objects. They rely on garbage collection finalizers for cleanup, which is unreliable:
file = Tempfile.new('data')
file.write "content"
file.close
file.unlink # Must call explicitly!
The finalizer may not run if the process crashes or is killed. Always explicitly close and unlink when not using blocks:
This is why Tempfile.new is usually a compatibility choice rather than a first choice. It still works, but it asks more of the caller and leaves more room for cleanup bugs. When you do use the manual form, wrapping the work in a begin/ensure block gives you the same cleanup guarantee that the block form provides automatically. The ensure clause runs regardless of whether the main code finishes normally or raises, so the file is always closed and removed. The if file.path guard handles the edge case where the file was already unlinked earlier.
file = Tempfile.new('data')
begin
file.write stuff
ensure
file.close
file.unlink if file.path
end
Unlink patterns
The unlink method removes the file from the filesystem while keeping the file handle open. This is useful when you want to make the file invisible to other processes but still need to read from or write to it through the open file descriptor. Linux and macOS both support this pattern, though Windows may behave differently. After unlinking, file.path returns nil even though the file handle is still usable, which makes the operation safe for privacy-sensitive data that should not be discoverable on disk.
file = Tempfile.create('test')
puts file.path
# => "/tmp/test20260318-xxxxxx"
file.unlink
puts file.path
# => nil (path returns nil after unlink)
puts file.closed?
# => false (file handle still open)
file.close
# Now you can still read/write through the handle
This is useful when you want the file gone from disk but still need to work with its contents in memory.
That technique is especially useful for privacy-sensitive work or for files that should exist only long enough for a downstream process to read them. It is a small detail, but it gives you more control over how long the file remains visible.
Common gotchas
GC timing is unpredictable
Tempfile objects register finalizers with Ruby’s garbage collector. Files may persist until GC runs — or not at all if references remain:
# May leave orphan files
file = Tempfile.new('data')
file.write stuff
file.close
# File might stay until GC runs
# Explicit cleanup - reliable
file = Tempfile.new('data')
begin
file.write stuff
ensure
file.close
file.unlink
end
Tempfile is not thread-safe
If multiple threads access the same Tempfile, wrap operations in a mutex:
require 'tempfile'
mutex = Mutex.new
file = Tempfile.create('shared')
threads = 5.times.map do |i|
Thread.new do
mutex.synchronize { file.write "Thread #{i}\n" }
end
end
threads.each(&:join)
file.rewind
puts file.read
Unlink-before-close on Windows
Calling unlink before closing works on POSIX systems but silently fails on Windows. The file remains until the handle closes:
file = Tempfile.create('test')
file.unlink # Works on Linux/macOS, may not on Windows
file.close
Different platforms make different promises here, so it is worth testing the behavior on the same operating systems your users actually run. Tempfile is portable, but the cleanup semantics are still shaped by the filesystem underneath it.
Summary
| Method | Return Type | Cleanup | Best For |
|---|---|---|---|
Tempfile.create | File | Block form or explicit | Most cases |
Tempfile.new | Tempfile | Manual or GC | Legacy compatibility |
Dir.tmpdir | String | N/A | Finding temp directory |
Dir.mktmpdir | String | Block form or explicit | Temporary workspaces |
Best practices:
- Prefer block forms for automatic, predictable cleanup
- Use
Tempfile.createoverTempfile.new - Explicitly close and unlink when not using blocks
- Use
anonymous: truefor sensitive data
Temporary storage is one of those areas where a little discipline pays off quickly. If you pick the block forms first and keep cleanup near the code that creates the resource, your scripts stay simpler and your production code leaves less behind when something goes wrong.
See Also
- Ruby Core Classes — Overview of Ruby’s built-in classes including File and IO
- Ruby String Methods — Working with string data in Ruby
- Ruby Kernel Methods — Essential kernel methods like
requireandputs