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.
# 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 -dOr 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:latestAccess at http://localhost:8080
# 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| 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.
| 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) |
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 mmapNote: 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.
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-data2. 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/test4. 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
| 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.
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-passwordThen 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$base64hashDocker 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.
Snipo offers three authentication modes to suit different deployment scenarios:
Normal mode with login page and password protection:
SNIPO_MASTER_PASSWORD=your-secure-password
# or
SNIPO_MASTER_PASSWORD_HASH=$argon2id$...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
Disable all authentication and password requirements when deploying behind an external authentication layer:
SNIPO_DISABLE_AUTH=trueSecurity 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.
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
Snipo features powerful fuzzy search that searches across:
- Snippet titles, descriptions, and content
- Multi-file snippet contents
- File names
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.
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
Mix search with filters for precise results:
?q=api&tag_id=1&language=python
Searches for "api" in Python snippets with tag 1.
?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.
Share code snippets publicly with granular file-level access.
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
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.mdURL 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
Snipo supports two-way synchronization with GitHub Gists, allowing you to backup your snippets to GitHub and keep them in sync across platforms.
-
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)
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
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
- Requires GitHub Personal Access Token (no OAuth)
- Sync is per-snippet, not automatic for new snippets
- GitHub API rate limit: 5000 requests/hour
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.
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/.
Set the SNIPO_BASE_PATH environment variable to your desired subpath:
SNIPO_BASE_PATH=/snipoImportant notes:
- The path should start with
/but not end with/ - Examples:
/snipo,/apps/snippets,/code - Leave empty (default) for root path deployment
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=trueyourdomain.com {
handle /snipo/* {
reverse_proxy localhost:8080
}
}Configuration:
SNIPO_BASE_PATH=/snipohttp:
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=/sniposervices:
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: trueSnipo 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.
See the Development Guide for build instructions, testing, and contribution guidelines.
