README
¶
Snipo
A lightweight, self-hosted snippet manager designed for single-user deployments.
Note: This project is intentionally scoped for single-user use. Multi-user features are not planned.
Quick Start
Docker (Recommended)
# Create environment file
cat > .env << EOF
SNIPO_MASTER_PASSWORD=your-secure-password
SNIPO_SESSION_SECRET=$(openssl rand -hex 32)
SNIPO_ENCRYPTION_SALT=$(openssl rand -base64 32)
EOF
# Run with Docker Compose
docker compose up -d
Or using Docker directly:
docker run -d \
-p 8080:8080 \
-v snipo-data:/data \
-e SNIPO_MASTER_PASSWORD=your-secure-password \
-e SNIPO_SESSION_SECRET=$(openssl rand -hex 32) \
-e SNIPO_ENCRYPTION_SALT=$(openssl rand -base64 32) \
--name snipo \
ghcr.io/mohamedelashri/snipo:latest
Access at http://localhost:8080
Binary
# Download latest release
curl -LO https://github.com/MohamedElashri/snipo/releases/latest/download/snipo_linux_amd64.tar.gz
tar xzf snipo_linux_amd64.tar.gz
# Configure and run
export SNIPO_MASTER_PASSWORD="your-secure-password"
export SNIPO_SESSION_SECRET=$(openssl rand -hex 32)
export SNIPO_ENCRYPTION_SALT=$(openssl rand -base64 32)
./snipo serve
Configuration
Essential
| Variable | Required | Default | Description |
|---|---|---|---|
SNIPO_MASTER_PASSWORD |
Yes* | - | Login password (plain text) |
SNIPO_MASTER_PASSWORD_HASH |
Yes* | - | Pre-hashed password (Argon2id) - recommended |
SNIPO_DISABLE_AUTH |
No | false |
Disable authentication entirely |
SNIPO_SESSION_SECRET |
Yes | - | Session signing key (32+ chars) |
SNIPO_ENCRYPTION_SALT |
Recommended | Auto-generated | Encryption key for backups & GitHub tokens |
SNIPO_PORT |
No | 8080 |
Server port |
SNIPO_DB_PATH |
No | /data/snipo.db |
SQLite database path |
SNIPO_BASE_PATH |
No | - | Base path for reverse proxy (e.g., /snipo) |
*Either SNIPO_MASTER_PASSWORD or SNIPO_MASTER_PASSWORD_HASH is required (unless SNIPO_DISABLE_AUTH=true). Using the hash is recommended for security.
Database Configuration
| Variable | Default | Description |
|---|---|---|
SNIPO_DB_PATH |
/data/snipo.db |
SQLite database path |
SNIPO_DB_MAX_CONNS |
1 |
Maximum database connections |
SNIPO_DB_BUSY_TIMEOUT |
5000 |
Database busy timeout (ms) |
SNIPO_DB_JOURNAL |
WAL |
Journal mode (WAL/DELETE/MEMORY) |
SNIPO_DB_SYNC |
NORMAL |
Synchronous mode (OFF/NORMAL/FULL) |
SNIPO_DB_MMAP_SIZE |
268435456 |
Memory-mapped I/O size (256MB) |
SNIPO_DB_CACHE_SIZE |
-2000 |
Cache size in KB (2MB, negative = KB) |
Database Memory Settings
If you encounter "out of memory" database errors, reduce the memory settings:
# For systems with limited memory
SNIPO_DB_MMAP_SIZE=67108864 # 64MB instead of 256MB
SNIPO_DB_CACHE_SIZE=-1000 # 1MB instead of 2MB
# For very constrained systems
SNIPO_DB_MMAP_SIZE=33554432 # 32MB
SNIPO_DB_CACHE_SIZE=-500 # 512KB
# Disable memory-mapped I/O if issues persist
SNIPO_DB_MMAP_SIZE=0 # Disable mmap
Note: The SQLite "out of memory (14)" error is misleading - it's about SQLite's internal memory allocation, not system RAM. Reducing these values typically resolves the issue.
Database Permission Issues
The "out of memory (14)" error can also be caused by filesystem permission problems:
Common Causes:
- Docker volume mount with insufficient permissions
- Read-only filesystem preventing database creation
- User/UID mismatch between host and container
Solutions:
1. Fix Docker Volume Permissions:
# Ensure the data directory has proper permissions
sudo chown -R 1000:1000 /path/to/your/data
sudo chmod -R 755 /path/to/your/data
# Or use a bind mount with proper ownership
mkdir -p ./snipo-data
chmod 755 ./snipo-data
2. Docker Compose Configuration:
services:
snipo:
volumes:
- ./snipo-data:/data # Ensure host directory is writable
# Remove user mapping if causing permission issues
# user: "1000:1000"
3. Check Volume Mount:
# Verify the container can write to the data directory
docker run --rm -v ./snipo-data:/data alpine touch /data/test
4. Use Named Volumes (Recommended):
services:
snipo:
volumes:
- snipo_data:/data # Docker manages permissions
volumes:
snipo_data:
Troubleshooting Steps:
- Check container logs for permission errors
- Verify the data directory exists and is writable
- Test with a simple file write operation
- Consider using Docker named volumes instead of bind mounts
API Configuration
| Variable | Default | Description |
|---|---|---|
SNIPO_RATE_LIMIT_READ |
1000 |
API read operations (per hour) |
SNIPO_RATE_LIMIT_WRITE |
500 |
API write operations (per hour) |
SNIPO_RATE_LIMIT_ADMIN |
100 |
API admin operations (per hour) |
SNIPO_ALLOWED_ORIGINS |
- | CORS allowed origins (comma-separated) |
SNIPO_ENABLE_PUBLIC_SNIPPETS |
true |
Enable public snippet sharing |
SNIPO_ENABLE_API_TOKENS |
true |
Enable API token creation |
SNIPO_ENABLE_BACKUP_RESTORE |
true |
Enable backup/restore |
See .env.example for all available options including S3 backup configuration.
Password Security
For enhanced security, use a pre-hashed password instead of plain text:
# Generate a password hash
./snipo hash-password your-secure-password
# Or with Docker
docker run --rm ghcr.io/mohamedelashri/snipo:latest hash-password your-secure-password
Then use the generated hash:
# In .env file
SNIPO_MASTER_PASSWORD_HASH=$argon2id$base64salt$base64hash
# Or in docker-compose.yml
environment:
- SNIPO_MASTER_PASSWORD_HASH=$argon2id$base64salt$base64hash
Docker Compose Warning: When using
SNIPO_MASTER_PASSWORD_HASHin docker-compose.yml, the$characters in Argon2id hashes will be interpreted as variable substitution. Either:
- Use double dollar signs:
$$argon2id$$base64salt$$base64hash- Quote the value:
"SNIPO_MASTER_PASSWORD_HASH=$argon2id$base64salt$base64hash"- Use a
.envfile and reference it:SNIPO_MASTER_PASSWORD_HASH=${SNIPO_MASTER_PASSWORD_HASH}
Benefits of using hashed passwords:
- Password never appears in plain text in config files
- Safer for version control (if you encrypt/secure the hash)
- Prevents accidental password exposure in logs or process listings
- Backward compatible - plain text passwords still work
See SECURITY.md for detailed password security practices.
Disabling Authentication
Snipo offers three authentication modes to suit different deployment scenarios:
1. Full Authentication (Default)
Normal mode with login page and password protection:
SNIPO_MASTER_PASSWORD=your-secure-password
# or
SNIPO_MASTER_PASSWORD_HASH=$argon2id$...
2. Login Disabled (Settings Option)
Hide the login page while maintaining security for sensitive operations. Useful for:
- Private networks (Tailscale, WireGuard) where login UI is unnecessary
- Trusted environments where you want easy access but protected token management
- Auth proxy deployments where external authentication handles access control
Configuration:
- Log in to your Snipo instance
- Go to Settings → General
- Enable "Disable Login Page"
How it works:
- Web UI is accessible without logging in
- Login page redirects to home page
- All API operations work without authentication
- API token creation and deletion always require password verification for security
Security Model:
- Read/Write Operations: No password required
- Create API Token: Password required (prompted in UI)
- Delete API Token: Password required (prompted in UI)
- Change Settings: No password required
Example - Working with API tokens:
# Create token via API (password required in body)
curl -X POST http://localhost:8080/api/v1/tokens \
-H "Content-Type: application/json" \
-d '{
"name":"My Token",
"permissions":"admin",
"password":"your-secure-password"
}'
# Delete token via API (password required in body)
curl -X DELETE http://localhost:8080/api/v1/tokens/1 \
-H "Content-Type: application/json" \
-d '{"password":"your-secure-password"}'
# Use token for operations (no password needed)
curl http://localhost:8080/api/v1/snippets \
-H "Authorization: Bearer <api-token>"
Why this matters:
- Even if someone gains access to your session or network, they cannot create or delete API tokens without the master password
- Provides an additional security layer against session hijacking or XSS attacks
- Protects against unauthorized API token management
3. Authentication Completely Disabled (Environment Variable)
⚠️ DANGER: Use with extreme caution!
Disable all authentication and password requirements when deploying behind an external authentication layer:
SNIPO_DISABLE_AUTH=true
Security Impact:
- No login required - Direct access to web UI
- No password verification - API token operations don't require passwords
- No session authentication - All API endpoints are open
- Complete trust in external authentication layer
Only use this when:
- Behind a trusted authentication proxy (Authelia, Authentik, OAuth2 Proxy, Cloudflare Access)
- In a completely isolated local environment with no network access
- For development/testing purposes
Never use this when:
- Directly exposed to the internet
- In untrusted networks
- Without understanding the complete security implications
- Unless you have a properly configured authentication proxy
See SECURITY.md for detailed guidance and best practices.
API
Create API tokens in Settings → API Tokens with granular permissions:
- read: View snippets, tags, folders
- write: Create, update, delete resources
- admin: Full access including settings
Authenticate via:
Authorization: Bearer <token>X-API-Key: <key>
All responses include metadata (request ID, timestamp, version) and pagination for lists.
API documentation:
- OpenAPI spec:
docs/openapi.yaml - Interactive docs:
http://localhost:8080/api/v1/openapi.json
Search
Snipo features powerful fuzzy search that searches across:
- Snippet titles, descriptions, and content
- Multi-file snippet contents
- File names
Basic Search
Type keywords in the search bar. Multiple words are matched using AND logic:
python docker
Finds snippets containing both "python" and "docker" anywhere in the metadata or content.
Filters
By Tags:
?tag_id=1 # Single tag
?tag_ids=1,2,3 # Multiple tags
By Folders:
?folder_id=1 # Single folder
?folder_ids=1,2,3 # Multiple folders
By Language:
?language=javascript
By Status:
?favorite=true # Favorites only
?is_archived=true # Archived snippets
Combining Filters
Mix search with filters for precise results:
?q=api&tag_id=1&language=python
Searches for "api" in Python snippets with tag 1.
Sorting
?sort=title&order=asc # A-Z by title
?sort=updated_at # Recently updated (default)
?sort=created_at # Recently created
In-app help: Click the ? icon next to the search bar for interactive documentation.
Public Snippets
Share code snippets publicly with granular file-level access.
Making Snippets Public
From Editor:
- Open or create a snippet
- Toggle the "Public" switch in the editor header
- Share the generated URL:
https://localhost:8080/s/{snippet-id}
From Preview Page:
- Click the globe icon in the header to toggle public/private status
- Requires authentication to change visibility
Accessing Public Snippets
Web Interface:
- Multi-file snippets display with file tabs
- Switch between files using the tab interface
- Download individual files with the download button
- Copy file URLs for direct access
Direct File Access (wget/curl):
For single-file snippets:
# Download the file
curl -O https://localhost:8080/api/v1/snippets/public/{snippet-id}/files/{filename}
# Or with wget
wget https://localhost:8080/api/v1/snippets/public/{snippet-id}/files/{filename}
For multi-file snippets:
# Download specific file
curl -O https://localhost:8080/api/v1/snippets/public/abc123/files/config.yaml
# Download all files using the file URLs from the web interface
curl -O https://localhost:8080/api/v1/snippets/public/abc123/files/main.go
curl -O https://localhost:8080/api/v1/snippets/public/abc123/files/README.md
URL Format:
- Snippet preview:
/s/{snippet-id} - Individual file (raw):
/api/v1/snippets/public/{snippet-id}/files/{filename}
Permissions:
- Public snippets are accessible without authentication
- View count is tracked automatically
- Files are returned as plain text with proper Content-Disposition headers
GitHub Gist Sync
Snipo supports two-way synchronization with GitHub Gists, allowing you to backup your snippets to GitHub and keep them in sync across platforms.
Setup
-
Generate GitHub Personal Access Token:
- Go to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
- Create a token with
gistscope - Copy the generated token
-
Configure in Snipo:
- Go to Settings → GitHub Gist tab
- Paste your GitHub token
- Click "Save Configuration" (sync is automatically enabled)
Features
Sync Options:
- Enable Sync for All: Creates GitHub gists for all your snippets at once
- Sync Now: Syncs changes for already-enabled snippets
- Auto-Sync: Background sync at configurable intervals (5/15/30/60 minutes)
Conflict Resolution:
- Manual: Review and resolve conflicts manually
- Snipo Wins: Always keep Snipo version
- Gist Wins: Always keep GitHub version
- Newest Wins: Keep the most recently modified version
Metadata Preservation:
- Snippet titles, descriptions, and content are synced
- Snipo-specific metadata (favorites, folders, tags) embedded in gist description
- Multi-file snippets fully supported
Usage
Enable sync for all snippets:
Settings → GitHub Gist → Enable Sync for All
View synced snippets:
- See list of synced snippets with status badges (✓ synced, ⟳ pending, ⚠ conflict, ✗ error)
- Click gist links to view on GitHub
- Remove mappings to stop syncing specific snippets
Manage conflicts:
- Conflicts appear when both Snipo and GitHub versions are modified
- Choose "Keep Snipo" or "Keep Gist" to resolve
- Or set automatic conflict resolution strategy in settings
Limitations
- Requires GitHub Personal Access Token (no OAuth)
- Sync is per-snippet, not automatic for new snippets
- GitHub API rate limit: 5000 requests/hour
Security
Container Security:
- Runs as non-root user (UID 1000)
- Read-only root filesystem
- All Linux capabilities dropped
- No privilege escalation allowed
Production Recommendations:
- Use strong passwords (16+ characters)
- Enable HTTPS via reverse proxy (
Nginx/Caddy/Traefik) - Configure CORS restrictively (
SNIPO_ALLOWED_ORIGINS) - Use Docker secrets for sensitive values
- Enable S3 backups with encryption
- Keep image updated regularly
See Development Guide for detailed security configuration.
Reverse Proxy Configuration
Snipo supports deployment behind a reverse proxy with a custom subpath. This is useful when you want to host Snipo under a specific path like https://yourdomain.com/snipo/.
Configuration
Set the SNIPO_BASE_PATH environment variable to your desired subpath:
SNIPO_BASE_PATH=/snipo
Important notes:
- The path should start with
/but not end with/ - Examples:
/snipo,/apps/snippets,/code - Leave empty (default) for root path deployment
Nginx Example
location /snipo/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Then configure Snipo:
SNIPO_BASE_PATH=/snipo
SNIPO_TRUST_PROXY=true
Caddy Example
yourdomain.com {
handle /snipo/* {
reverse_proxy localhost:8080
}
}
Configuration:
SNIPO_BASE_PATH=/snipo
Traefik Example
http:
routers:
snipo:
rule: "Host(`yourdomain.com`) && PathPrefix(`/snipo`)"
service: snipo
middlewares:
- snipo-stripprefix
middlewares:
snipo-stripprefix:
stripPrefix:
prefixes:
- "/snipo"
services:
snipo:
loadBalancer:
servers:
- url: "http://localhost:8080"
Configuration:
SNIPO_BASE_PATH=/snipo
Docker Compose with Reverse Proxy
services:
snipo:
image: ghcr.io/mohamedelashri/snipo:latest
environment:
- SNIPO_MASTER_PASSWORD=your-secure-password
- SNIPO_SESSION_SECRET=${SESSION_SECRET}
- SNIPO_BASE_PATH=/snipo
- SNIPO_TRUST_PROXY=true
volumes:
- snipo-data:/data
networks:
- proxy-network
volumes:
snipo-data:
networks:
proxy-network:
external: true
Customization
Snipo supports extensive visual customization through custom CSS. Users can personalize the interface by:
- Overriding color schemes and CSS variables
- Customizing component styles (sidebar, editor, modals)
- Creating unique themes and visual effects
Access via Settings → Appearance → Custom CSS. See the Customization Guide for detailed documentation, examples, and best practices.
Development
See the Development Guide for build instructions, testing, and contribution guidelines.