A production-ready IPFS node built on Kubo with Caddy as a reverse proxy for authentication, deployed via Kamal.
A single Docker image bundles two services:
- Kubo v0.40.0 — IPFS daemon providing content pinning, IPNS publishing, and a public gateway
- Caddy — reverse proxy adding basic auth to the admin API and handling large uploads
┌─────────────────────────────────┐
│ Docker Container │
│ │
ipfs.1001.digital ───▶│ :8080 Kubo Gateway (public) │
│ read-only, pinned only │
│ │
admin.ipfs.1001.digital▶│ :5080 Caddy ──▶ :5001 Kubo API│
│ basic auth │
│ │
swarm ◀──────▶│ :4001 Kubo Swarm (P2P) │
└─────────────────────────────────┘
- Public gateway serves only locally pinned content (
Gateway.NoFetch = true) - Admin API protected by basic auth via Caddy — supports the full Kubo HTTP API
- Unlimited upload size with streaming support for large files
- IPNS records with a 14-day lifetime to survive extended downtime
- Web UI automatically pinned on startup for admin access
- Persistent storage via a Docker volume (
ipfs_data) or configurable bind mount
- Docker
- Kamal (
gem install kamal) - pnpm
- A server with ports 4001, 8080, and 5080 available
- Copy the environment template and fill in your values:
cp .env.production.example .env.production| Variable | Description |
|---|---|
DOCKER_REGISTRY_USERNAME |
Docker Hub username |
DEPLOY_HOST |
Target server IP or hostname |
KAMAL_REGISTRY_PASSWORD |
Docker Hub access token |
IPFS_HOST |
Public gateway domain |
IPFS_ADMIN_HOST |
Admin API domain |
ADMIN_PASSWORD |
Plaintext admin password (used by the upload script) |
ADMIN_PASSWORD_HASH |
Bcrypt-hashed admin password (used by Caddy) |
- Generate a bcrypt password hash for admin access:
caddy hash-password --plaintext 'your-password'- Install dependencies and run initial setup:
pnpm install
pnpm kamal:setupThese optional environment variables can be set in .env.production to tune node behavior:
| Variable | Default | Description |
|---|---|---|
GATEWAY_NO_FETCH |
true |
When true, only serves pinned/cached content. Set to false to fetch from the IPFS network on demand. |
GATEWAY_DESERIALIZED_RESPONSES |
true |
Enables directory listings and deserialized responses. |
IPNS_RECORD_LIFETIME |
336h |
How long IPNS records remain valid. Default is 14 days. |
STORAGE_MAX |
20GB |
Maximum disk space for the IPFS datastore. |
ENABLE_GC |
true |
Enable automatic garbage collection. Frees unpinned content when storage exceeds the GC watermark. |
STORAGE_GC_WATERMARK |
90 |
Percentage of STORAGE_MAX that triggers garbage collection. |
GC_PERIOD |
1h |
How often the daemon checks whether to run garbage collection. |
CONN_MGR_HIGH_WATER |
96 |
Maximum number of peer connections to maintain. |
CONN_MGR_LOW_WATER |
32 |
Peer connections to trim down to when HighWater is reached. |
IPFS_VOLUME |
ipfs_data |
Storage volume for IPFS data. Use a host path (e.g. /mnt/ipfs/ipfs_data) for bind mounts. |
CONTAINER_CPUS |
2 |
CPU cores available to the Docker container. |
CONTAINER_MEMORY |
6G |
Maximum memory for the Docker container. |
RESOURCE_MGR_MAX_MEMORY |
4GB |
Maximum memory for the libp2p resource manager. Should be less than CONTAINER_MEMORY to leave headroom for Caddy, GC, and other overhead. |
RESOURCE_MGR_MAX_FILE_DESCRIPTORS |
4096 |
Maximum file descriptors for the libp2p resource manager. |
pnpm kamal:deployThis builds the Docker image, pushes it to Docker Hub, and deploys to your server. Kamal handles zero-downtime deploys, health checks, and TLS.
Upload files or directories to the node's MFS (Mutable File System) via the admin API:
# Upload a directory
pnpm upload ./dist
# Upload a single file
pnpm upload ./image.png
# Pin the content after uploading
pnpm upload ./dist --pin
# Specify a custom MFS path (defaults to /<name>)
pnpm upload ./dist --mfs-path /my-siteRequires IPFS_ADMIN_HOST and ADMIN_PASSWORD in .env.production. Set IPFS_HOST to use your own gateway in the output URL (defaults to ipfs.io).
Each upload is logged to uploads.log with the timestamp, CID, MFS path, and source path.
Access pinned content over HTTP:
https://ipfs.1001.digital/ipfs/<CID>
https://ipfs.1001.digital/ipns/<name>
Only locally pinned content is served — the gateway does not fetch from the network.
The full Kubo RPC API is available behind basic auth:
# Pin content
curl -u admin:<password> -X POST \
"https://admin.ipfs.1001.digital/api/v0/pin/add?arg=<CID>"
# Add a file
curl -u admin:<password> -X POST -F file=@myfile.txt \
"https://admin.ipfs.1001.digital/api/v0/add"
# Publish an IPNS record
curl -u admin:<password> -X POST \
"https://admin.ipfs.1001.digital/api/v0/name/publish?arg=<CID>"pnpm kamal:sh├── Dockerfile # Multi-stage: Caddy + Kubo
├── config/
│ ├── Caddyfile # Reverse proxy config
│ ├── deploy.yml # Kamal deployment manifest
│ └── container-init.d/
│ ├── 001-server-config.sh # IPFS daemon configuration
│ ├── 002-admin-proxy.sh # Start Caddy
│ └── 003-pin-webui.sh # Pin Web UI on startup
├── .kamal/
│ └── hooks/
│ ├── pre-deploy # Stop containers to free ports
│ └── post-deploy # Register admin proxy
├── scripts/
│ └── upload.ts # Upload files/directories to IPFS
├── package.json # Kamal deployment & upload scripts
└── .env.production.example # Environment template
MIT