Skip to content
Hoody.com

Every container has /ramdisk unless you explicitly disable it: temporary storage backed by RAM instead of disk. It suits hot caches, small build artifacts, and temporary processing where I/O speed is the constraint.


There are two separate reasons to reach for /ramdisk, and they call for different data:

Because it is fast

It is RAM, so I/O-bound scratch work stops waiting on storage. Anything written, read a few times, and thrown away belongs here.

Because it never touches disk

A security property, not a performance one: what you write here is never on a disk, in a snapshot, or in a backup. That is what /ramdisk/secrets (private, mode 0700) exists for.

Build and test scratch: compiler and bundler intermediates, test fixtures, coverage output. Every run regenerates them, so losing them costs nothing:

Terminal window
mkdir -p /ramdisk/tmp && export TMPDIR=/ramdisk/tmp

Decrypted credentials at runtime: decrypt a key, or exchange a short-lived token, straight into /ramdisk/secrets. The plaintext exists only in memory: it is never written to disk, captured by a snapshot, or present on a seized drive.

Terminal window
# Plaintext lives in RAM only
(umask 077; decrypt-secret > /ramdisk/secrets/deploy.key)
trap 'rm -f /ramdisk/secrets/deploy.key' EXIT

Here a host reboot wiping the file is a feature, not a risk: the secret cannot outlive the machine that held it.

Caches whose authoritative copy lives elsewhere: rendered fragments, thumbnails, session data. After a host reboot a cold cache is a slow request, not a lost record.

Media and data processing scratch: transcode intermediates, image-pipeline stages, sort spill files.

Database temp space for a heavy one-off query: keep the temporary b-trees out of storage.

Terminal window
mkdir -p /ramdisk/tmp
export SQLITE_TMPDIR=/ramdisk/tmp

(For Postgres, the equivalent is a temp tablespace pointed at a directory under /ramdisk.)

Handing an artifact to a sibling container: not available today. This is the one case that would justify ramdisk_scope: project, but the API does not currently accept that value on create or PATCH (see ramdisk_scope), so /ramdisk/project cannot be obtained on a new container. Use a storage share or a database in /hoody/databases/ instead.

Working recipes for these live in Use cases below.


Container configuration:

File access:


Capacity: one shared pool

Per server, not per container:

  • Pool size: 512 MiB by default, a per-server value you cannot set yourself
  • Clamped to 50% of that server’s memory, so a small server’s pool is smaller
  • Read the real ceiling from ramdisk.shared_pool_maximum on GET /api/v1/containers/{id}/stats, or with df -h /ramdisk
  • Nothing is reserved for a container: first come, first served
  • Actual RAM use is only what you store; an empty ramdisk uses 0 bytes

RAM is consumed as files are written and freed when they are deleted.

Speed: RAM performance

Orders of magnitude faster than disk:

  • Read: ~10-20 GB/s
  • Write: ~10-20 GB/s
  • Latency: <1µs

vs. SSD:

  • Read: ~0.5-3 GB/s
  • Write: ~0.5-2 GB/s
  • Latency: ~50-100µs

Survives container restarts

Tied to the server, not the container:

  • Persists through container stop/start
  • Persists through container restart
  • Cleared on host reboot: the directory tree is rebuilt empty at boot

Data survives container operations, not host reboots.

Your files stay private

Each container gets its own directories:

  • Container A’s /ramdisk and container B’s are separate trees
  • /ramdisk/secrets (mode 0700) for credential material
  • /ramdisk/project exists only with ramdisk_scope: project (a value the API does not currently accept) and is shared only with your containers in that project on the same server

Capacity is shared even where the files are not.

Memory, not disk

Charged to your server’s memory:

  • Counts against RAM; it appears in no disk total
  • Mounted noswap: the kernel cannot page these pages out
  • This is why the pool is capped conservatively: everything you store is RAM your own processes can no longer use

There are three paths, depending on scope:

PathWho can read/write itNotes
/ramdiskThis container onlyYour general-purpose scratch space
/ramdisk/secretsThis container only, mode 0700The designated place for credential material; only the container’s own root can open it
/ramdisk/projectYour containers in this project on the same serverPresent only when ramdisk_scope is project, a value the API does not currently accept

ramdisk_scope is container (the default) or project, but project is not currently accepted from callers, so container is the only value you can set today.

Today, /ramdisk and /ramdisk/secrets are private to each container. New containers cannot obtain /ramdisk/project, although containers that already hold ramdisk_scope: project may still have it.

Terminal window
# Refused today with 400
PATCH /api/v1/containers/{id}
{"ramdisk_scope": "project"}

Rules enforced today for containers that already hold project:

  • A RAM disk cannot span servers: project means same project and same server. Containers of one project placed on different servers each get their own, non-shared /ramdisk/project.
  • ramdisk_scope must be container when ramdisk is false; a shared scope with no ramdisk is ambiguous and is rejected.
  • Narrowing (project to container) is rejected while the container is running; stop it first so the shared mount can be verifiably removed before the change is recorded.

When project is re-enabled, widening (container to project) will work on a running container.


Terminal window
# Create container (ramdisk enabled by default)
hoody containers create --project $PROJECT_ID --server-id $SERVER_ID --name "my-container" --hoody-kit
# Create container with ramdisk explicitly disabled
hoody containers create --project $PROJECT_ID --server-id $SERVER_ID --name "no-ramdisk" --hoody-kit --no-ramdisk
POST Create container with ramdisk enabled (default behavior)
/api/v1/projects/{project_id}/containers
Click "Run" to execute the request

/ramdisk is available immediately and consumes no RAM while it is empty.

Check ramdisk status:

Terminal window
GET /api/v1/containers/{id}
# Response: "ramdisk": true (enabled) or false (disabled)

Access it like any directory:

Terminal window
# In container (via terminal or SSH)
cd /ramdisk
# Create directories
mkdir -p /ramdisk/cache
mkdir -p /ramdisk/builds
# Write files
echo "data" > /ramdisk/cache/session-abc.json
# Read files
cat /ramdisk/cache/session-abc.json
# Credentials go in the 0700 directory
(umask 077; printf '%s' "$API_KEY" > /ramdisk/secrets/api.key)
# Your own footprint
du -sh /ramdisk
# The pool: size and used are server-wide, not this container's
df -h /ramdisk

Files in /ramdisk are stored in RAM; reads and writes never touch disk.


Terminal window
# Put the cache in ramdisk, not node_modules, which rarely fits in 512 MiB
export NPM_CONFIG_CACHE=/ramdisk/npm-cache
cd /home/user/project
npm install # Tarball cache reads/writes stay in RAM
# Then copy final artifacts to persistent storage
cp -r dist /hoody/storage/production/

Cache-heavy installs get much faster, as long as what you put in /ramdisk fits the pool.

Terminal window
# Application cache in RAM
mkdir -p /ramdisk/app-cache
# Store frequently accessed data (a small database; the pool is 512 MiB)
cp /hoody/databases/users.db /ramdisk/app-cache/
sqlite3 /ramdisk/app-cache/users.db "SELECT ..." # Served from RAM
# Session storage
echo '{"user": 1, "token": "abc"}' > /ramdisk/sessions/user-1.json

Cache hits return in <1ms.

Process files without disk I/O, keeping each chunk well under the 512 MiB pool:

Terminal window
# Download a dataset chunk to ramdisk
curl "https://data.example.com/dataset.csv" > /ramdisk/dataset.csv
# Process in RAM (no disk writes)
awk -F',' '{sum+=$3} END {print sum}' /ramdisk/dataset.csv > /ramdisk/result.txt
# Upload the result, then free the pool immediately
curl -X POST "https://api.example.com/results" \
-d "@/ramdisk/result.txt"
rm -f /ramdisk/dataset.csv /ramdisk/result.txt

Temporary files here cause no disk wear.

Work in batches. A full frame dump will overflow a 512 MiB pool long before the video ends:

Terminal window
# Extract a window of frames to ramdisk (burst I/O)
ffmpeg -ss 00:00:10 -t 5 -i video.mp4 /ramdisk/frames/frame_%04d.png
# Process frames (parallel reads)
for frame in /ramdisk/frames/*.png; do
convert $frame -resize 50% $frame
done
# Merge back to video
ffmpeg -i /ramdisk/frames/frame_%04d.png output.mp4

Workloads made of thousands of small file operations benefit most from RAM speed.


There is no per-container ramdisk quota to plan. Your server gets one tmpfs pool, and every container of yours on that server draws from it:

Your server:
- Pool size: 512 MiB by default (a per-server value, not a per-container one)
- Hard ceiling: 50% of that server's memory; the configured size is clamped to it,
so a small server's pool is smaller than 512 MiB
- Reserved per container: nothing; first come, first served
- Empty ramdisk: 0 bytes of RAM consumed

Capacity is not allocation: RAM is consumed on demand and freed the moment files are deleted.

Because nothing is reserved, one container can consume the whole pool:

Terminal window
# Example for a server whose reported pool ceiling is 512 MiB.
Container A writes 500 MiB to /ramdisk ~12 MiB left for everyone else
Container B tries to write 50 MiB "No space left on device"

When the pool is full:

  • Writes fail immediately with ENOSPC; nothing silently degrades to disk
  • Containers keep running; only the write fails
  • Deleting files in any container on that server frees the space again

There is no swap fallback. The pool is mounted noswap, so these pages can never be paged out. That is deliberate (RAM disk contents stay in RAM), but it means the space you occupy is space your own processes cannot have.

Example planning:

Terminal window
# Example for a server whose reported pool ceiling is 512 MiB.
# Scenario A: One build container (comfortable)
# package cache in /ramdisk: ~200 MiB, cleared after each build
# Scenario B: Four small services (comfortable)
# each keeps ~50 MiB of hot cache = ~200 MiB total
# Scenario C: Tight
# two containers each holding 240 MiB; the pool is effectively full,
# and a third container has nowhere to write

Monitor your own footprint with du -sh /ramdisk; use df -h /ramdisk (or the stats endpoint) to see how much of the shared pool is left.

From the API, measured per scope:

Terminal window
GET /api/v1/containers/{id}/stats
# The ramdisk field sits beside `memory` (it is memory, never disk):
# "ramdisk": {
# "scope": "container",
# "shared_pool_maximum": 536870912, # the shared ceiling for this server; null if unconfirmed
# "capacity_reserved": false, # always false: nothing is held for you
# "usage": { "private": 4096, "secrets": 0 }
# }
  • shared_pool_maximum is the whole pool, which co-tenant containers of yours also draw from. It is not your quota, so do not turn it into a percentage.
  • usage is null on the container list endpoint (measuring costs a host round trip per container). That means “open the container to measure”, not “empty”.
  • With scope: project, usage.project is the shared directory’s size. It is shared with sibling containers, so adding it to private double-counts.

From inside the container:

Terminal window
du -sh /ramdisk # what this container is holding
df -h /ramdisk # the shared pool: total size and how much is left

If the pool is close to full:

  1. Delete finished work: rm -rf /ramdisk/build
  2. Disable ramdisk on containers that never use it
  3. Move anything larger than a working set to container storage

Terminal window
POST /api/v1/containers/{id}/{operation} # operation=restart

Persists:

  • All files in /ramdisk remain
  • Directory structure intact
  • No data loss

Traditional ramdisks clear on reboot. Hoody’s /ramdisk persists through container operations.


Try ramdisk first, fall back to disk:

import os
def get_cached_data(key):
ramdisk_path = f'/ramdisk/cache/{key}.json'
disk_path = f'/hoody/storage/cache/{key}.json'
# Try ramdisk first (fast)
if os.path.exists(ramdisk_path):
return read_file(ramdisk_path)
# Fall back to disk
if os.path.exists(disk_path):
data = read_file(disk_path)
# Promote to ramdisk for next access
write_file(ramdisk_path, data)
return data
# Cache miss
return None

Build in ramdisk, save the final output to disk:

#!/bin/bash
# Build script; keep the intermediate tree inside the 512 MiB pool
# Compile in ramdisk (fast)
cd /ramdisk/build
cmake ..
make -j$(nproc)
# Test binary (fast startup from RAM)
./test-suite
# Copy only the final binary to persistent storage
cp binary /hoody/storage/production/app-v1.2.3
# Free the pool for your other containers as soon as you are done
rm -rf /ramdisk/build

Sessions in RAM get fast reads and writes plus automatic expiry:

// Sessions in ramdisk (fast read/write)
const sessionPath = `/ramdisk/sessions/${sessionId}.json`;
// Write session
fs.writeFileSync(sessionPath, JSON.stringify({userId, token, expiresAt}));
// Read session
const session = JSON.parse(fs.readFileSync(sessionPath));
// A host reboot clears sessions automatically (no stale sessions)

How /ramdisk compares to SSD and HDD:

Writing a 256 MiB file (a 1 GB file does not fit the 512 MiB pool):

StorageWrite speedTime
/ramdisk~15 GB/s~0.02s
SSD~2 GB/s~0.13s
HDD~200 MB/s~1.3s

RAM is 7-70x faster.


Ramdisk is enabled automatically, but you can opt out at creation time:

Terminal window
# Disable ramdisk when creating the container
POST /api/v1/projects/{id}/containers
{
"ramdisk": false // Explicitly disable at creation
}

When to disable:

  • Simple APIs (CRUD operations, no heavy I/O)
  • Static file servers
  • Long-running daemons with minimal disk access
  • Containers that would only compete for a pool their siblings need

When to keep it enabled (the default):

  • Build servers (compilation, npm install)
  • Cache servers (Redis-like workloads)
  • Media processing (video/image transcoding)
  • Data processing (ETL, analytics)

An enabled ramdisk holding no files consumes zero RAM, so disable it only when you are certain the container will not benefit.

Never rely on /ramdisk for critical data:

Terminal window
# Good: temporary processing
wget https://example.com/dataset.zip -O /ramdisk/dataset.zip
unzip /ramdisk/dataset.zip -d /ramdisk/processing/
# Process and save results to /hoody/storage
# Bad: long-term storage
cp important-data.db /ramdisk/ # Lost on host reboot

If data matters after a host reboot, do not put it only in /ramdisk.

The pool is shared and its ceiling is fixed per server (512 MiB by default, clamped to 50% of server memory), so clean up the moment a task finishes:

#!/bin/bash
# Build script with cleanup
# Build in ramdisk
npm install --prefix /ramdisk/build
npm run build --prefix /ramdisk/build
# Copy final bundle
cp /ramdisk/build/dist/*.js /hoody/storage/production/
# Clean up immediately (free RAM)
rm -rf /ramdisk/build
# Or clean on exit
trap 'rm -rf /ramdisk/build' EXIT
Terminal window
# The shared pool: how full it is for everyone on this server
df -h /ramdisk
# Alert if the pool is >80% full
USAGE=$(df /ramdisk | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $USAGE -gt 80 ]; then
echo "WARNING: shared ramdisk pool >80% full"
fi
# Your own share of it
du -sh /ramdisk

Integrate with hoody-notifications for alerts.

If your app requires ramdisk, say so in your README:

## Runtime requirements
- `ramdisk: true` (the default). Frame extraction writes ~200 MiB of PNGs to
`/ramdisk/frames` in batches, then deletes them. Remember the pool is
shared with our other containers on this server; check its real ceiling
with `df -h /ramdisk` (512 MiB by default).

Future maintainers know why ramdisk is enabled and what it costs the pool.


Yes:

  • RAM: ~15 GB/s throughput, <1µs latency
  • SSD: ~2 GB/s throughput, ~50-100µs latency
  • 10-50x faster for I/O-intensive workloads

512 MiB by default, and that is the whole pool, shared by your containers on that server. It is not a per-container allowance but a per-server value, and whatever is configured is clamped to 50% of that server’s memory, so a small server’s pool is smaller than 512 MiB. The authoritative figure for your container is ramdisk.shared_pool_maximum on GET /api/v1/containers/{id}/stats (bytes, or null when no ceiling is confirmed), or df -h /ramdisk from inside.

Actual RAM consumption is only what is stored:

Terminal window
# Example for a server whose reported pool ceiling is 512 MiB.
df -h /ramdisk
# Filesystem Size Used Avail Use% Mounted on
# tmpfs 512M 120M 392M 24% /ramdisk
# ^^^^ ^^^^ ---- pool capacity vs pool usage
# Pool Used by all your containers on this server

An empty pool shows its full size but consumes 0 bytes of RAM; memory is allocated on demand. For this container’s own footprint, use du -sh /ramdisk.

Not through the container API. The size is a per-server setting: there is no size parameter on container create or PATCH, no dashboard control, and no per-container override, and it can never exceed 50% of that server’s memory. If your workload needs more room, stage it on container storage and keep only the hot working set in /ramdisk.

Why does data survive restarts but not host reboots?

Section titled “Why does data survive restarts but not host reboots?”

Container restart: the server keeps the pool mounted while the container stops and starts, so the same RAM data is there afterwards.

Host reboot: powering off clears all RAM. When the host powers back on, the pool is recreated and the directory tree is rebuilt empty.

This is a physical property of RAM: power loss is data loss.

Yes:

Terminal window
# Stop container
POST /api/v1/containers/{id}/{operation} # operation=stop
# Disable ramdisk
PATCH /api/v1/containers/{id}
{"ramdisk": false}
# Start container
POST /api/v1/containers/{id}/{operation} # operation=start
# operation enum: start | stop | force-stop | restart | pause | resume
# /ramdisk no longer available (RAM freed)

Warning: any data in /ramdisk is lost when you disable it.

The same as any full filesystem:

  • “No space left on device” errors
  • Applications fail to write
  • Nothing spills over to disk, and nothing is swapped out; the write simply fails

It is one pool: a sibling container of yours on the same server can fill it, and freeing space in any of them helps all of them.

Solution:

Terminal window
# Delete old files
rm -rf /ramdisk/old-cache/*
# Or clear everything in this container
rm -rf /ramdisk/*
# See how much of the pool is left
df -h /ramdisk

Not on anything you create today. Sharing needs ramdisk_scope: project, and the API refuses that value on both create and PATCH (400, “ramdisk_scope: project is temporarily unavailable”), so a new container cannot be given a shared /ramdisk/project. Containers that already hold the scope keep it.

When it is re-enabled, these rules still hold:

  • /ramdisk and /ramdisk/secrets are always private to a single container
  • A RAM disk cannot span servers: containers of the same project on different servers get separate, non-shared /ramdisk/project directories
  • It cannot be shared through storage shares

Workaround for anything wider:

Terminal window
# Copy from ramdisk to persistent storage
cp /ramdisk/data.json /hoody/storage/shared/
# Share persistent storage instead
POST /api/v1/containers/{id}/storage/shares {"source_path": "/hoody/storage/shared", "target_container_id": "TARGET_CONTAINER_ID", "mode": "readwrite"}

Or use a shared concurrent-write database in /hoody/databases/.


Problem: the /ramdisk directory does not exist

Solutions:

  1. Verify ramdisk is enabled:

    Terminal window
    GET /api/v1/containers/{id}
    # Check: "ramdisk": true
  2. If false, enable it:

    Terminal window
    POST /api/v1/containers/{id}/{operation} # operation=stop
    PATCH /api/v1/containers/{id} {"ramdisk": true}
    POST /api/v1/containers/{id}/{operation} # operation=start
    # operation enum: start | stop | force-stop | restart | pause | resume
  3. Restart the container if the status shows true but the directory is missing:

    Terminal window
    POST /api/v1/containers/{id}/{operation} # operation=restart
  4. Check whether the placement has a pool. In rare placements a container has no RAM pool to attach and therefore no /ramdisk. GET /api/v1/containers/{id}/stats omits the ramdisk field entirely in that case.

Problem: /ramdisk/project does not exist

Cause: /ramdisk/project only exists when ramdisk_scope is project, and that scope cannot be set today; the API refuses it on both create and PATCH:

Terminal window
GET /api/v1/containers/{id}/stats
# "ramdisk": { "scope": "container", ... } ← no /ramdisk/project
PATCH /api/v1/containers/{id} {"ramdisk_scope": "project"}
# 400: `ramdisk_scope: project` is temporarily unavailable

Nothing about your project changes that: the refusal is unconditional, not a reaction to the project’s members. Move the data with a storage share or /hoody/databases/ instead.

If the container already holds scope: project and the directory is still missing, check the server: a RAM disk cannot span servers. Containers of the same project on different servers each get their own /ramdisk/project, and they do not see each other’s files.

Problem: writes to /ramdisk fail even though the container has free memory

Cause: the shared pool is full, often filled by one of your other containers on that server.

Debug:

Terminal window
df -h /ramdisk # pool: Size / Used / Avail across all your containers here
du -sh /ramdisk # what this container is contributing

Solutions:

  1. Immediate: delete finished work, in this container or any sibling on that server

    Terminal window
    rm -rf /ramdisk/build /ramdisk/old-cache
  2. Long-term: disable ramdisk on containers that never use it

    Terminal window
    PATCH /api/v1/containers/{id} {"ramdisk": false}
  3. Structural: move anything bigger than a working set to container storage. The pool cannot be enlarged.

Problem: files existed yesterday, now missing

Likely causes:

  • The host server rebooted; the pool is recreated empty at boot
  • With ramdisk_scope: project, one of your sibling containers deleted them from the shared /ramdisk/project

/ramdisk is cleared on host reboot, not container reboot.

Prevention:

  • Never store critical data only in /ramdisk
  • Always copy important results to persistent storage
  • Document ramdisk as temporary storage in your app

Storage:

Performance tuning:

What to remember:

  • /ramdisk is enabled by default (set ramdisk: false to disable); unavailable in rare placements with no RAM pool
  • RAM is consumed on demand; an empty ramdisk uses 0 bytes
  • Capacity is one shared pool per server: 512 MiB by default, not something you can set, clamped to 50% of that server’s memory; nothing is reserved per container
  • It is memory, not disk: charged to your server’s RAM, never paged out (noswap)
  • /ramdisk and /ramdisk/secrets (mode 0700) are private; /ramdisk/project appears only with ramdisk_scope: project, a value the API does not currently accept
  • RAM-speed storage, 10-50x faster than SSD
  • Persists through container restarts
  • Cleared on host reboot; the tree is rebuilt empty, because RAM does not survive power loss
  • Watch du -sh /ramdisk for your footprint and df -h /ramdisk for what is left of the pool

Budget against the shared pool, clean up early, and use /ramdisk for speed, not persistence.