Build a Fake GPU Docker Container
Build a Docker container that fakes nvidia-smi output for GPU testing.
Introduction
30 Second Summary
Testing GPU-dependent scripts and monitoring tools usually requires expensive hardware. But when you just need to verify that your parsing logic handles nvidia-smi output correctly, a real GPU is overkill.
In this project, you will build a custom Docker container that impersonates an NVIDIA GPU workstation, producing pixel-perfect nvidia-smi output configurable at runtime through environment variables.
What You'll Build
Run nvidia-smi inside your container and get back a realistic GPU status table that would fool any script or tool expecting real hardware.
By the end of this project, you'll have:
- A running container where nvidia-smi prints a formatted GPU status table with driver version, CUDA version, temperature, power, memory usage, and utilization.
- Multi-flag support (--version, -L) so tools that probe GPU availability get the responses they expect.
- Runtime configuration via docker run -e flags to swap GPU model, memory size, and utilization without rebuilding the image.
- Secret Mission: Add --query-gpu CSV output support so the container can fool GPU monitoring pipelines that parse machine-readable data.
Are there any prerequisites?
You need Docker Desktop installed on Windows 10 and basic familiarity with Docker commands and shell scripting.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Create the Project Structure and Base Script
Tools that monitor GPUs all depend on one thing: the output of nvidia-smi. If your fake version nails the output format, anything parsing it downstream won't know the difference.
In this step, you'll create a project folder, write a bash script that produces a pixel-perfect nvidia-smi table, and wrap it in a Docker container you can run immediately.
In this step, get ready to:
- Create the project directory on your Desktop.
- Write a bash script that outputs a hardcoded nvidia-smi GPU status table.
- Build a Docker image and run it to see the output.
Set up the project folder
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir fake-gpu
cd fake-gpu
Write the fake nvidia-smi script
The real nvidia-smi outputs a bordered table with driver info, GPU stats, and a processes section. Your script needs to reproduce that format exactly.
- Create a new file called nvidia-smi (no file extension) inside your fake-gpu folder.
- Paste the following content as the first section of the file. This sets up the function and the GPU info table:
#!/bin/bash
# Fake nvidia-smi - simulates NVIDIA GPU status output
show_default() {
local timestamp
timestamp=$(date '+%a %b %d %H:%M:%S %Y')
cat <<EOF
${timestamp}
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce RTX 4090 On | 00000000:01:00.0 Off | N/A |
| N/A 38C P0 25W / 450W | 0MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
What does this code do?
- show_default() is a function that prints the full nvidia-smi table. Wrapping it in a function makes it easy to add other output modes later.
- timestamp=$(date ...) generates a real timestamp so the output looks live every time you run it.
- cat <<EOF starts a heredoc. Everything between <<EOF and the closing EOF is printed verbatim (with variable expansion).
- The bordered table mirrors the exact column layout of real nvidia-smi output: driver version header, GPU row with name/temp/power/memory/utilization, and separator lines.
- Directly below the last line you just pasted (the +----...----+ row), add the processes section that completes the heredoc and closes the function:
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+
EOF
}
show_default
What does this section add?
The processes table is the bottom half of real nvidia-smi output. It normally lists GPU processes, but since this is a fake GPU with no real workloads, it reports "No running processes found".
EOF closes the heredoc, } closes the function, and the final show_default line calls the function when the script runs.
- Save the nvidia-smi file.
- Confirm the file exists by running:
ls
You should see nvidia-smi listed in the output.
✔️ Awesome, I've got everything!
Great. Make sure the file is saved before moving on.
ⓧ I'd like to double check the full code
Your nvidia-smi file is built from the two chunks above. Scroll up and confirm:
- Chunk 1 (the shebang, function start, and GPU info table) is at the top of the file.
- Chunk 2 (the processes section, EOF, closing brace, and show_default call) follows immediately after with no gap between the table rows.
- There is exactly one blank line between the GPU table close and the Processes table open (inside the heredoc).
Write the Dockerfile and build the image
The Dockerfile wraps your script into a container image. It copies the script into the system PATH so running nvidia-smi inside the container executes your fake version.
- Create a new file called Dockerfile (no extension) in your fake-gpu folder with the following content:
FROM ubuntu:24.04
LABEL maintainer="fake-gpu"
LABEL description="Ubuntu container with fake nvidia-smi"
COPY nvidia-smi /usr/local/bin/nvidia-smi
RUN chmod +x /usr/local/bin/nvidia-smi
CMD ["nvidia-smi"]
What does this Dockerfile do?
- FROM ubuntu:24.04 uses Ubuntu Noble as the base image, which gives you bash out of the box.
- COPY nvidia-smi /usr/local/bin/nvidia-smi places your script in a directory already on the container's PATH. No PATH manipulation needed.
- RUN chmod +x makes the script executable inside the container.
- CMD ["nvidia-smi"] means running the container with no arguments automatically executes your fake nvidia-smi.
- Save the Dockerfile.
- Confirm both files are present by running:
ls
You should see both Dockerfile and nvidia-smi listed.
✔️ Awesome, I've got everything!
Great. Make sure both files are saved before building.
ⓧ I'd like to double check the full code
FROM ubuntu:24.04
LABEL maintainer="fake-gpu"
LABEL description="Ubuntu container with fake nvidia-smi"
COPY nvidia-smi /usr/local/bin/nvidia-smi
RUN chmod +x /usr/local/bin/nvidia-smi
CMD ["nvidia-smi"]
- Build the Docker image by running:
docker build -t fake-gpu .
You should see Docker pull the ubuntu:24.04 base image (if not cached), copy your script, and output a success message with the image ID.
Build failed?
- Check that both Dockerfile and nvidia-smi are in the same directory and that your terminal is inside that directory.
- Make sure Dockerfile has no file extension. Some editors add .txt silently.
- Confirm Docker Desktop is running. You should see the whale icon in your system tray.
Help me debug my Docker build failure.
Before you run the container, what do you think the output will look like? Will it print plain text, or something formatted?
- Run the container to see your fake nvidia-smi in action:
docker run --rm fake-gpu
You should see a fully formatted nvidia-smi table printed to your terminal. It includes a timestamp, driver version header, GPU row with the RTX 4090 name, temperature, power, memory, utilization, and the processes section at the bottom.
Seeing a permission denied or script error?
- Make sure the very first line of your nvidia-smi file is exactly #!/bin/bash with no spaces before it.
- If you see garbled output or a syntax error, your editor may have saved the file with Windows line endings (CRLF). Convert it to LF line endings in your editor's settings.
- Rebuild after any fix with docker build -t fake-gpu . before running again.
Help me fix my fake nvidia-smi container.
Your fake GPU is alive. Right now it only prints the default table with hardcoded values. Next up, you'll add flag support so the container handles --version and -L just like the real nvidia-smi.
Support Multiple Command Flags
Your fake nvidia-smi prints the default table, but that's only one of several outputs the real binary produces. Monitoring scripts call nvidia-smi -L to discover GPUs, and CI jobs call nvidia-smi --version to check driver compatibility.
Without these flags, anything that probes for GPU availability before doing real work will bail out. In this step, you'll add argument parsing so the script responds correctly to multiple flags.
In this step, get ready to:
- Add version and GPU listing output functions to the script.
- Wire up a case statement for argument routing.
- Rebuild the image and verify each flag produces correct output.
Add the version and GPU listing functions
Each flag needs its own output function. The --version flag prints a one-line driver/CUDA string, and -L lists GPUs with their UUIDs.
- Open nvidia-smi in your editor.
- Find the show_default() function. Add the following two functions above it:
show_version() {
echo "NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6"
}
show_list_gpus() {
echo "GPU 0: NVIDIA GeForce RTX 4090 (UUID: GPU-12345678-abcd-1234-efgh-123456789abc)"
}
What do these functions do?
- show_version() prints the same one-line header the real nvidia-smi --version produces: NVIDIA-SMI version, driver version, and CUDA version on a single line.
- show_list_gpus() outputs the GPU index, name, and a UUID string. The real binary lists each physical GPU this way when you pass -L.
- Save nvidia-smi.
Wire up argument parsing with a case statement
The functions exist but nothing calls them yet. You need a routing block that inspects the first argument and dispatches to the right function.
- In nvidia-smi, scroll to the very bottom of the file. Find this line:
show_default
- Delete that line and replace it with the following case block:
# Parse arguments
case "$1" in
--version|-v)
show_version
;;
-L|--list-gpus)
show_list_gpus
;;
"")
show_default
;;
*)
echo "nvidia-smi: Unknown option '$1'"
echo "Try 'nvidia-smi --help' for more information."
exit 1
;;
esac
What does this case block do?
- $1 is the first argument passed to the script. The case statement matches it against known patterns.
- --version|-v matches either the long or short version flag.
- The empty-string pattern "" catches the no-argument case and shows the full table (same as before).
- The * wildcard catches anything unrecognized and exits with code 1, signaling failure to the caller.
- Save nvidia-smi.
Rebuild and test each flag
- Rebuild the image by running:
docker build -t fake-gpu .
Before you test, what do you expect to see when you run the container with nvidia-smi --version?
- Test the --version flag by running:
docker run --rm fake-gpu nvidia-smi --version
You should see a single line:
NVIDIA-SMI 560.35.03 Driver Version: 560.35.03 CUDA Version: 12.6
- Test the -L flag by running:
docker run --rm fake-gpu nvidia-smi -L
You should see the GPU listed with its UUID:
GPU 0: NVIDIA GeForce RTX 4090 (UUID: GPU-12345678-abcd-1234-efgh-123456789abc)
- Test an unknown flag by running:
docker run --rm fake-gpu nvidia-smi --bogus
You should see an error message and the container exits with code 1:
nvidia-smi: Unknown option '--bogus'
Try 'nvidia-smi --help' for more information.
- Finally, confirm the default display still works by running:
docker run --rm fake-gpu
You should see the same bordered GPU table from Step 1.
Seeing 'exec format error' or no output?
- Make sure the first line of your nvidia-smi script is exactly #!/bin/bash with no leading spaces or Windows line endings.
- Check that you deleted the bare show_default call at the bottom. If it still exists alongside the case block, the table will always print regardless of flags.
- Confirm you rebuilt the image after saving. The running container uses the image from the last docker build, not your live file.
Why isn't my fake nvidia-smi responding to flags correctly?
✔️ Awesome, I've got everything!
Great. Double check you've saved nvidia-smi and that your last docker build completed without errors.
ⓧ I'd like to double check the full code
Your nvidia-smi file should have these sections in order. Scroll up and confirm each is present:
- The shebang line #!/bin/bash and the comment header.
- show_version() function printing the one-line version string.
- show_list_gpus() function printing the GPU index, name, and UUID.
- show_default() function with the full bordered table (unchanged from Step 1).
- The case "$1" block at the bottom routing --version|-v, -L|--list-gpus, empty string, and the * wildcard.
The Dockerfile is unchanged from Step 1.
Your container now handles the flags that real-world scripts probe for. Next up, you'll replace all those hardcoded values with environment variables so you can simulate any GPU model without rebuilding the image.
Make It Configurable with Environment Variables
Your fake nvidia-smi works, but every value is baked into the script. Want to simulate an A100 with 80GB of memory? You'd have to edit the file and rebuild the image.
Environment variables solve this. By reading configuration from the environment at runtime, you can swap the entire GPU personality with docker run -e flags. One image, infinite GPU configurations.
In this step, get ready to:
- Add ENV defaults to the Dockerfile for all GPU parameters.
- Update the script to read values from environment variables with fallback defaults.
- Test the container with custom GPU configurations at runtime.
Add environment variable defaults to the Dockerfile
The ENV instruction in a Dockerfile sets default values that persist into the running container. The learner can override any of them at runtime with docker run -e.
- Open Dockerfile in your editor and replace its entire contents with this:
FROM ubuntu:24.04
LABEL maintainer="fake-gpu"
LABEL description="Ubuntu container with fake nvidia-smi"
ENV GPU_NAME="NVIDIA GeForce RTX 4090"
ENV GPU_MEMORY_TOTAL="24564MiB"
ENV GPU_MEMORY_USED="0MiB"
ENV GPU_TEMP="38"
ENV GPU_POWER_DRAW="25W"
ENV GPU_POWER_CAP="450W"
ENV GPU_UTILIZATION="0"
ENV DRIVER_VERSION="560.35.03"
ENV CUDA_VERSION="12.6"
COPY nvidia-smi /usr/local/bin/nvidia-smi
RUN chmod +x /usr/local/bin/nvidia-smi
CMD ["nvidia-smi"]
What does this change?
The nine ENV lines between the LABELs and COPY inject default values into the container's environment. The script will read these at runtime.
Anyone running the image can override any variable with -e GPU_NAME="NVIDIA A100" without touching the Dockerfile or rebuilding.
- Save Dockerfile.
- Rebuild the image to confirm the Dockerfile is still valid by running:
docker build -t fake-gpu .
You should see the build complete successfully. The output won't change yet because the script still uses hardcoded strings.
Build failing?
- Check that each ENV line uses double quotes around the value, not single quotes.
- Confirm the nvidia-smi file is still in the same directory as your Dockerfile.
Help me debug my Dockerfile build error
✔️ Awesome, I've got everything!
Great. Make sure you've saved Dockerfile before moving on.
ⓧ I'd like to double check the full code
FROM ubuntu:24.04
LABEL maintainer="fake-gpu"
LABEL description="Ubuntu container with fake nvidia-smi"
ENV GPU_NAME="NVIDIA GeForce RTX 4090"
ENV GPU_MEMORY_TOTAL="24564MiB"
ENV GPU_MEMORY_USED="0MiB"
ENV GPU_TEMP="38"
ENV GPU_POWER_DRAW="25W"
ENV GPU_POWER_CAP="450W"
ENV GPU_UTILIZATION="0"
ENV DRIVER_VERSION="560.35.03"
ENV CUDA_VERSION="12.6"
COPY nvidia-smi /usr/local/bin/nvidia-smi
RUN chmod +x /usr/local/bin/nvidia-smi
CMD ["nvidia-smi"]
Update the script to read from environment variables
Bash parameter expansion lets a variable fall back to a default when the environment doesn't provide a value. The syntax ${VAR:-default} means "use the environment's value if set, otherwise use default."
- Open nvidia-smi and replace the entire file contents. Start with the header, variable declarations, memory computation, and the two small functions:
#!/bin/bash
# Fake nvidia-smi - simulates NVIDIA GPU status output
# Configurable via environment variables
# Defaults (overridden by ENV)
GPU_NAME="${GPU_NAME:-NVIDIA GeForce RTX 4090}"
GPU_MEMORY_TOTAL="${GPU_MEMORY_TOTAL:-24564MiB}"
GPU_MEMORY_USED="${GPU_MEMORY_USED:-0MiB}"
GPU_TEMP="${GPU_TEMP:-38}"
GPU_POWER_DRAW="${GPU_POWER_DRAW:-25W}"
GPU_POWER_CAP="${GPU_POWER_CAP:-450W}"
GPU_UTILIZATION="${GPU_UTILIZATION:-0}"
DRIVER_VERSION="${DRIVER_VERSION:-560.35.03}"
CUDA_VERSION="${CUDA_VERSION:-12.6}"
# Compute free memory
mem_total_num="${GPU_MEMORY_TOTAL%MiB}"
mem_used_num="${GPU_MEMORY_USED%MiB}"
mem_free_num=$((mem_total_num - mem_used_num))
show_version() {
echo "NVIDIA-SMI ${DRIVER_VERSION} Driver Version: ${DRIVER_VERSION} CUDA Version: ${CUDA_VERSION}"
}
show_list_gpus() {
echo "GPU 0: ${GPU_NAME} (UUID: GPU-12345678-abcd-1234-efgh-123456789abc)"
}
What does this code do?
- Each ${VAR:-default} line reads from the container's environment first. If the variable isn't set, it falls back to the hardcoded default.
- ${GPU_MEMORY_TOTAL%MiB} strips the "MiB" suffix so you can do arithmetic to compute free memory.
- show_version() and show_list_gpus() now interpolate the variables instead of printing hardcoded strings.
- Directly below show_list_gpus(), add the updated show_default() function that uses variables in the table output:
show_default() {
local timestamp
timestamp=$(date '+%a %b %d %H:%M:%S %Y')
cat <<EOF
${timestamp}
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI ${DRIVER_VERSION} Driver Version: ${DRIVER_VERSION} CUDA Version: ${CUDA_VERSION} |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 ${GPU_NAME} On | 00000000:01:00.0 Off | N/A |
| N/A ${GPU_TEMP}C P0 ${GPU_POWER_DRAW} / ${GPU_POWER_CAP} | ${GPU_MEMORY_USED} / ${GPU_MEMORY_TOTAL} | ${GPU_UTILIZATION}% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+
EOF
}
What changed in show_default?
Every hardcoded value in the table is now a variable reference. The header line uses ${DRIVER_VERSION} and ${CUDA_VERSION}. The GPU row uses ${GPU_NAME}, ${GPU_TEMP}, ${GPU_POWER_DRAW}, ${GPU_MEMORY_USED}, and ${GPU_UTILIZATION}. Bash expands these inside the heredoc at runtime.
- Finally, add the argument parsing block below show_default(). This is identical to the previous step except for an added comment:
# Parse arguments
case "$1" in
--version|-v)
show_version
;;
-L|--list-gpus)
show_list_gpus
;;
"")
show_default
;;
*)
echo "nvidia-smi: Unknown option '$1'"
echo "Try 'nvidia-smi --help' for more information."
exit 1
;;
esac
- Save nvidia-smi.
Syntax errors when you test later?
- Make sure the heredoc delimiter EOF at the end of show_default() is on its own line with no leading whitespace.
- Check that every variable reference uses curly braces: ${GPU_NAME} not $GPU_NAME.
- Confirm the file starts with #!/bin/bash on line 1 with no blank lines above it.
Help me debug my nvidia-smi script
✔️ Awesome, I've got everything!
Great. Confirm you've saved nvidia-smi and move on to testing.
ⓧ I'd like to double check the full code
Your nvidia-smi file should contain these sections in order. Scroll up and confirm each chunk is present:
- Shebang, comments, variable declarations with ${VAR:-default} syntax, and memory computation.
- show_version() and show_list_gpus() functions using variable interpolation.
- show_default() function with the full bordered table using variables.
- The # Parse arguments case statement at the bottom.
Rebuild and test with custom GPU configurations
- Rebuild the image by running:
docker build -t fake-gpu .
- First, verify the defaults still work by running with no overrides:
docker run --rm fake-gpu
You should see the same RTX 4090 table as before. The output is identical because the Dockerfile's ENV defaults match the script's fallback defaults.
Now for the real test. Before you run the next command, predict what you'll see in the GPU Name and Memory columns.
- Run the container with an A100 configuration by passing environment overrides:
docker run --rm -e GPU_NAME="NVIDIA A100" -e GPU_MEMORY_TOTAL="81920MiB" fake-gpu
The table now shows "NVIDIA A100" in the GPU Name column and "81920MiB" as total memory. Everything else stays at the RTX 4090 defaults.
- Confirm the flags also pick up overrides by running:
docker run --rm -e GPU_NAME="NVIDIA A100" fake-gpu nvidia-smi -L
You should see GPU 0: NVIDIA A100 (UUID: GPU-12345678-abcd-1234-efgh-123456789abc) in the output.
Still seeing hardcoded RTX 4090 values?
- Make sure you rebuilt the image after saving both files. The old image still has the old script baked in.
- Check that your -e flags come before the image name fake-gpu in the command. Docker ignores -e flags placed after the image name.
- Verify that the script's variable declarations use :- (colon dash) not just - (dash alone). The colon means "use default if empty OR unset."
Help me debug my environment variable overrides
Your fake GPU container is now fully configurable. One image can impersonate any NVIDIA GPU just by changing runtime flags. Next up, the Secret Mission takes this further with machine-readable CSV output that fools monitoring pipelines.
Secret mission
Add --query-gpu CSV Output
Many GPU monitoring tools don't parse the human-readable table. They call nvidia-smi --query-gpu=... --format=csv to get machine-readable output. Can you add CSV query support to fool real monitoring pipelines?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- Docker image named fake-gpu.
- Project directory containing Dockerfile and nvidia-smi script.
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend the fake GPU container with more features.
- Your fake-gpu image stays in your local Docker cache, ready to run anytime.
- Your project files remain on your Desktop for future edits.
Pause - I'll come back to this later
The Docker image takes up disk space but costs nothing to keep. If you want to free up space without losing your project files, remove just the image.
- Remove the Docker image by running:
docker rmi fake-gpu
Your project files stay on disk. When you are ready to resume, rebuild the image with docker build -t fake-gpu . from the project directory.
Delete - I don't want to use this again
Remove all project resources and start fresh.
- Remove the Docker image by running:
docker rmi fake-gpu
- Delete the project directory from your Desktop.
macOS
rm -rf ~/Desktop/fake-gpu
Windows
Remove-Item -Recurse -Force ~\Desktop\fake-gpu
- Confirm the image is gone by running:
docker images | grep fake-gpu
You should see no output, confirming the image has been removed.
Nice Work!
Nice Work!
You just built a fully functional fake GPU container from scratch. Anyone running nvidia-smi inside your container sees output indistinguishable from a real NVIDIA workstation.
You've learned how to:
- Build a custom Docker image from ubuntu:24.04 with a fake nvidia-smi script placed in the system PATH so it runs like the real binary.
- Add multi-flag argument parsing to a shell script so it handles the default display, --version, and -L just like the real binary.
- Make a container's output fully runtime-configurable using environment variables and docker run -e flags to swap GPU personality without rebuilding the image.
- Secret Mission: Added --query-gpu CSV output parsing that matches the real nvidia-smi CSV format, making the container a drop-in mock for monitoring pipelines.
Ready to quiz yourself?