Deploying Cannery
Introduction
Cannery is a self-hosted firearm and ammunition tracker application that helps you manage your ammunition inventory with ease and security. Built with Elixir and Phoenix, Cannery provides a lightweight, secure platform for tracking ammunition containers, ammo packs, and usage records. Whether you’re a shooting enthusiast, gun collector, or recreational shooter, Cannery gives you complete control over your ammunition management data by running on your own infrastructure. Deploy Cannery on Klutch.sh to create a private, self-hosted ammunition tracking system that keeps your inventory data secure and accessible only to you.
Key Features
- Ammunition Container Management: Create and organize custom containers to store and categorize your ammunition
- Custom Tags: Tag containers and ammunition types for easy organization and filtering
- Ammunition Type Tracking: Add custom ammunition types with specifications and details
- Ammo Pack Management: Add ammunition packs to containers and track quantity and condition
- Stage Ammo for Range Day: Mark ammunition packs as staged for upcoming shooting sessions
- Shot Records: Track ammunition usage with detailed shot records from range days
- Invite Token System: Share your instance with trusted users via invite tokens
- Public Registration Option: Optionally enable public registration for open access instances
- Multi-language Support: Support for English (US), German, French, and Spanish locales
- User Invitations: Create and manage user invitations with tokens
- Admin User Management: First created user is automatically promoted to admin
- Secure Self-Hosting: Keep all your ammunition data on your own infrastructure
- Data Privacy: Complete control over your data with no external cloud dependencies
- Responsive Web Interface: Access Cannery from any internet-capable device
- PostgreSQL Database: Robust database backend for reliable data storage
- Email Notifications: SMTP-based email system for invitations and notifications
- Configuration Flexibility: Extensive environment variables for customization
- Multi-locale Support: Localized interface in multiple languages
- Container Organization: Organize ammunition by storage location or type
- Usage Tracking: Monitor ammunition consumption over time
- Backup Ready: Easy backup and recovery with PostgreSQL
- API-Ready Architecture: Built on Phoenix framework for potential API extensions
- Performance Optimization: Connection pooling and efficient database queries
Prerequisites
Before deploying Cannery on Klutch.sh, ensure you have:
- A Klutch.sh account with access to the dashboard
- A GitHub repository for your Cannery deployment (public or private)
- An SMTP email service configured for sending invitations and notifications
- Understanding of Elixir and Phoenix framework concepts (helpful but not required)
- A persistent volume for PostgreSQL database storage
Important Considerations
Deployment Steps
Create a Dockerfile
Create a
Dockerfilein the root of your repository to containerize Cannery:FROM elixir:1.16-alpine as builder# Install build dependenciesRUN apk add --no-cache \build-base \git \nodejs \npm \postgresql-client# Set working directoryWORKDIR /app# Clone Cannery repositoryRUN git clone https://gitea.bubbletea.dev/shibao/cannery.git .# Install dependenciesRUN mix deps.get --only prodRUN npm install --prefix assetsRUN npm run deploy --prefix assets# Build releaseRUN mix phx.digestRUN mix release# Runtime stageFROM alpine:latest# Install runtime dependenciesRUN apk add --no-cache \openssl \postgresql-client \bash# Create app userRUN addgroup -g 1000 app && \adduser -D -u 1000 -G app app# Copy release from builderCOPY --from=builder /app/_build/prod/rel/cannery /appRUN chown -R app:app /app# Set working directoryWORKDIR /app# Switch to app userUSER app# Expose portEXPOSE 4000# Start commandCMD ["/app/bin/cannery", "start"]Create Environment Configuration File
Create a
.env.examplefile to document required environment variables:# Cannery Environment Configuration# Application Host and PortHOST=example-app.klutch.shPORT=4000# Secret Key Base (generate with: docker run -it shibaobun/cannery /app/priv/random.sh)SECRET_KEY_BASE=your-generated-secret-key-base-here# PostgreSQL Database ConfigurationDATABASE_URL=ecto://postgres:postgres@localhost/canneryPOOL_SIZE=10ECTO_IPV6=false# Registration Mode (invite or public)REGISTRATION=invite# Locale (en_US, de, fr, es)LOCALE=en_US# SMTP Configuration (Required for invitations)SMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_USERNAME=your-email@gmail.comSMTP_PASSWORD=your-app-passwordSMTP_SSL=false# Email SettingsEMAIL_FROM=no-reply@example-app.klutch.shEMAIL_NAME=Cannery# Optional: Environment modeMIX_ENV=prodCreate Configuration Directory
Create a
configdirectory with Cannery configuration files for production:Create
config/prod.exs:import Config# Configure your databaseconfig :cannery, Cannery.Repo,username: System.get_env("DATABASE_USER") || "postgres",password: System.get_env("DATABASE_PASSWORD") || "postgres",hostname: System.get_env("DATABASE_HOST") || "localhost",database: System.get_env("DATABASE_NAME") || "cannery",port: String.to_integer(System.get_env("DATABASE_PORT") || "5432"),pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),ssl: System.get_env("DATABASE_SSL") == "true"# For production, don't forget to configure the url host# to something meaningful, Phoenix uses this information# when generating URLs.config :cannery, CanneryWeb.Endpoint,url: [host: System.get_env("HOST") || "localhost"],http: [ip: {0, 0, 0, 0},port: String.to_integer(System.get_env("PORT") || "4000")],secret_key_base: System.get_env("SECRET_KEY_BASE")# Configure Mailerconfig :cannery, Cannery.Mailer,adapter: Swoosh.Adapters.SMTP,relay: System.get_env("SMTP_HOST"),port: String.to_integer(System.get_env("SMTP_PORT") || "587"),username: System.get_env("SMTP_USERNAME"),password: System.get_env("SMTP_PASSWORD"),ssl: System.get_env("SMTP_SSL") == "true",retries: 2,auth: :always# Cannery configurationconfig :cannery,registration: System.get_env("REGISTRATION") || "invite",locale: System.get_env("LOCALE") || "en_US",email_from: System.get_env("EMAIL_FROM") || "no-reply@localhost"Create Entrypoint Script
Create an
entrypoint.shfile to initialize the application:#!/bin/bashset -eecho "Starting Cannery..."# Generate SECRET_KEY_BASE if not providedif [ -z "$SECRET_KEY_BASE" ]; thenecho "Generating SECRET_KEY_BASE..."export SECRET_KEY_BASE=$(openssl rand -base64 48)fi# Wait for database to be readyecho "Waiting for database..."while ! nc -z $DATABASE_HOST 5432 2>/dev/null; dosleep 1done# Run migrationsecho "Running database migrations..."/app/bin/cannery migrate || true# Start applicationecho "Starting Cannery application..."exec /app/bin/cannery startMake it executable:
Terminal window chmod +x entrypoint.shCreate .gitignore File
Create a
.gitignorefile to exclude sensitive data from version control:# Environment and secrets.env.env.local.env.*.localSECRET_KEY_BASE# Elixir/Mix_build/deps/*.beamerl_crash.dump.fetch# Nodenode_modules/assets/node_modules/# Database*.sqlite*.sqlite3# IDE.vscode/.idea/*.swp*.swo*~.DS_Store# Generated filespriv/static/cover/doc/.elixir_ls/# Logs*.logCreate docker-compose.yml for Local Testing
Create a
docker-compose.ymlfile for local development and testing (not for Klutch.sh deployment):version: '3.8'services:cannery:build: .container_name: cannery-devports:- "4000:4000"environment:- DATABASE_HOST=cannery-db- DATABASE_NAME=cannery- DATABASE_USER=postgres- DATABASE_PASSWORD=postgres- HOST=localhost:4000- PORT=4000- SECRET_KEY_BASE=${SECRET_KEY_BASE:-changeme}- SMTP_HOST=smtp.gmail.com- SMTP_PORT=587- SMTP_USERNAME=${SMTP_USERNAME}- SMTP_PASSWORD=${SMTP_PASSWORD}- EMAIL_FROM=noreply@cannery.local- REGISTRATION=publicdepends_on:- cannery-dbnetworks:- cannery-networkcannery-db:image: postgres:16-alpinecontainer_name: cannery-dbenvironment:- POSTGRES_USER=postgres- POSTGRES_PASSWORD=postgres- POSTGRES_DB=canneryvolumes:- cannery_db_data:/var/lib/postgresql/datanetworks:- cannery-networkvolumes:cannery_db_data:networks:cannery-network:driver: bridgePush to GitHub
Push your repository to GitHub with all configuration files:
Terminal window git add Dockerfile entrypoint.sh .env.example \config/prod.exs docker-compose.yml .gitignoregit commit -m "Initial Cannery deployment configuration"git push origin mainDeploy on Klutch.sh
- Navigate to klutch.sh/app and log in to your dashboard
- Click Create New App
- Select Docker as the deployment method (detected automatically from your Dockerfile)
- Connect your GitHub repository containing the Cannery deployment files
- Confirm the Dockerfile is detected from your root directory
- Click Deploy to start the deployment process
- Wait for the deployment to complete and your app to become active
Configure Traffic Rules and Persistent Storage
- In your app dashboard, navigate to Traffic settings
- Select HTTP as the traffic type
- Set the internal port to 4000 (Cannery Phoenix server default)
- Save your traffic configuration
- For database persistence, navigate to Volumes settings
- Click Add Volume
- Enter mount path:
/var/lib/postgresql/data - Set volume size to 50GB (adjust based on expected number of ammunition records)
- Click Attach to create and mount the persistent volume
- Your Cannery application will be accessible at
example-app.klutch.sh
Initial Setup and Configuration
After deploying Cannery on Klutch.sh, configure your instance for first use.
Access Your Cannery Instance
- Navigate to
http://example-app.klutch.shin your browser - You’ll see the Cannery login page
- Click Register to create your first account
- The first user created will automatically be promoted to admin
- Log in with your new credentials
Configure SMTP Email
Cannery requires SMTP configuration for sending user invitations:
- Log in as admin
- Navigate to Settings or Admin Settings
- Configure SMTP details:
- SMTP Host: Your email provider’s SMTP server
- SMTP Port: Typically 587 (TLS) or 465 (SSL)
- Username: Your email address
- Password: Your email provider’s app password
- Email From: Address to send emails from
- Test the configuration by sending a test email
- Save your SMTP settings
Set Registration Mode
- Navigate to admin settings
- Choose registration mode:
- Invite Only (default): Users can only join with an invite token
- Public: Users can self-register without an invite
- Save your preference
Create Your First Container
- Navigate to the Containers section
- Click New Container
- Enter a name (e.g., “Home Safe”, “Range Bag”, “Storage Box”)
- Add optional tags for organization
- Click Create
Add Ammunition Types
- Go to Ammunition Types section
- Click New Type
- Enter ammunition specifications:
- Caliber/Type name
- Cartridge type
- Grain weight (if applicable)
- Save the ammunition type
Add Ammo Packs to Containers
- Select a container
- Click Add Ammo Pack
- Select the ammunition type
- Enter the quantity
- Add notes or date information
- Click Add
Create User Invitations
- Navigate to Users or Admin section
- Click Generate Invite Token
- Share the invite link with users
- Users can register using the invite token
- Manage user access from admin settings
Track Ammo Usage
- Before a range day, go to your containers
- Select ammo packs to stage for shooting
- Click Stage for Range Day
- After range day, create shot records
- Record ammunition used and shots fired
- Track your ammunition consumption over time
Backup Your Data
- Use PostgreSQL tools to backup your database:
Terminal window pg_dump cannery > cannery_backup.sql - Store backups securely
- Test restore procedures regularly
Environment Variables
Basic Configuration
Configure these essential environment variables for your Cannery deployment:
HOST=example-app.klutch.sh- External domain name for your Cannery instancePORT=4000- Internal port Cannery listens onSECRET_KEY_BASE- Generated secret key for session encryption (must be set)REGISTRATION=invite- Registration mode (invite or public)SMTP_HOST- SMTP server for emailSMTP_PORT=587- SMTP port (typically 587 for TLS)SMTP_USERNAME- Email account usernameSMTP_PASSWORD- Email account passwordEMAIL_FROM- Sender address for emailsLOCALE=en_US- Language locale (en_US, de, fr, es)
Production Environment Variables
For production deployments using Nixpacks, configure these environment variables for optimal performance:
# Application ConfigurationHOST=example-app.klutch.shPORT=4000MIX_ENV=prod
# Generate with: docker run -it shibaobun/cannery /app/priv/random.shSECRET_KEY_BASE=your-very-long-random-key-base-here
# Database ConfigurationDATABASE_HOST=localhostDATABASE_PORT=5432DATABASE_NAME=canneryDATABASE_USER=postgresDATABASE_PASSWORD=secure-password-herePOOL_SIZE=10ECTO_IPV6=false
# SMTP Configuration (Email)SMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_USERNAME=your-email@gmail.comSMTP_PASSWORD=your-app-specific-passwordSMTP_SSL=false
# Email SettingsEMAIL_FROM=noreply@example-app.klutch.shEMAIL_NAME=Cannery Ammunition Tracker
# Registration and LocalizationREGISTRATION=inviteLOCALE=en_US
# PerformanceLOG_LEVEL=noticePREFERRED_CLI_ENV=prodCode Examples
Elixir - Cannery Container Management
# Cannery Container Management Moduledefmodule CanneryManager do @moduledoc """ Module for managing Cannery containers and ammunition programmatically """
def create_container(%{user: user, name: name, tags: tags}) do %Cannery.Containers.Container{} |> Cannery.Containers.Container.changeset(%{ name: name, user_id: user.id, tags: tags }) |> Cannery.Repo.insert() end
def list_containers(%{user_id: user_id}) do Cannery.Repo.all( from c in Cannery.Containers.Container, where: c.user_id == ^user_id, order_by: [asc: c.name] ) end
def add_ammo_pack(%{container_id: container_id, ammo_type_id: ammo_type_id, count: count}) do %Cannery.Ammo.AmmoPack{} |> Cannery.Ammo.AmmoPack.changeset(%{ container_id: container_id, ammo_type_id: ammo_type_id, count: count }) |> Cannery.Repo.insert() end
def get_container_summary(%{container_id: container_id}) do container = Cannery.Repo.get!(Cannery.Containers.Container, container_id)
ammo_packs = Cannery.Repo.all( from p in Cannery.Ammo.AmmoPack, where: p.container_id == ^container_id )
total_count = Enum.sum(Enum.map(ammo_packs, & &1.count))
%{ container: container, pack_count: Enum.count(ammo_packs), total_ammo: total_count, packs: ammo_packs } end
def stage_for_range_day(%{ammo_pack_id: ammo_pack_id}) do pack = Cannery.Repo.get!(Cannery.Ammo.AmmoPack, ammo_pack_id)
pack |> Cannery.Ammo.AmmoPack.changeset(%{staged: true}) |> Cannery.Repo.update() end
def record_shot(%{ammo_pack_id: ammo_pack_id, shots_fired: shots}) do pack = Cannery.Repo.get!(Cannery.Ammo.AmmoPack, ammo_pack_id)
pack |> Cannery.Ammo.AmmoPack.changeset(%{ count: pack.count - shots, last_shot_date: Date.utc_today() }) |> Cannery.Repo.update() end
def export_containers_json(%{user_id: user_id, output_file: file}) do containers = list_containers(%{user_id: user_id})
data = Enum.map(containers, fn container -> summary = get_container_summary(%{container_id: container.id})
%{ name: container.name, tags: container.tags, pack_count: summary.pack_count, total_ammo: summary.total_ammo, created_at: container.inserted_at } end)
File.write!(file, Jason.encode!(data, pretty: true)) endendBash - Cannery Management Script
#!/bin/bash
# Cannery Administration and Management Script# Perform common management tasks via API calls
CANNERY_HOST="example-app.klutch.sh"CANNERY_URL="http://${CANNERY_HOST}"AUTH_TOKEN="${CANNERY_AUTH_TOKEN}"
# Color outputRED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[1;33m'BLUE='\033[0;34m'NC='\033[0m'
# Check if instance is onlinecheck_status() { echo -e "${BLUE}Checking Cannery instance status...${NC}"
if curl -s "${CANNERY_URL}" > /dev/null 2>&1; then echo -e "${GREEN}✓ Cannery is online${NC}" return 0 else echo -e "${RED}✗ Cannery is offline${NC}" return 1 fi}
# Create a new containercreate_container() { local name=$1 local tags=$2
if [ -z "$name" ]; then echo -e "${RED}Usage: create_container <name> [tags]${NC}" return 1 fi
echo -e "${YELLOW}Creating container: $name${NC}"
curl -s -X POST "${CANNERY_URL}/api/containers" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"container\": { \"name\": \"$name\", \"tags\": \"${tags:-}\" } }" | jq . 2>/dev/null || echo "Container created (jq not available)"}
# List all containerslist_containers() { echo -e "${BLUE}Listing all containers:${NC}"
curl -s "${CANNERY_URL}/api/containers" \ -H "Authorization: Bearer $AUTH_TOKEN" | \ jq '.data[] | {id: .id, name: .name, tag_count: (.tags | length)}' 2>/dev/null || \ curl -s "${CANNERY_URL}/api/containers" \ -H "Authorization: Bearer $AUTH_TOKEN"}
# Get container detailsget_container() { local container_id=$1
if [ -z "$container_id" ]; then echo -e "${RED}Usage: get_container <id>${NC}" return 1 fi
echo -e "${BLUE}Getting container details for $container_id:${NC}"
curl -s "${CANNERY_URL}/api/containers/${container_id}" \ -H "Authorization: Bearer $AUTH_TOKEN" | \ jq . 2>/dev/null || \ curl -s "${CANNERY_URL}/api/containers/${container_id}" \ -H "Authorization: Bearer $AUTH_TOKEN"}
# Generate invite tokengenerate_invite() { echo -e "${YELLOW}Generating user invite token...${NC}"
curl -s -X POST "${CANNERY_URL}/api/invites" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" | \ jq '.token' 2>/dev/null || echo "Invite token generated (jq not available)"}
# Export dataexport_data() { local output_file=${1:-cannery_export.json}
echo -e "${YELLOW}Exporting Cannery data...${NC}"
{ echo "{" echo " \"export_date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," echo " \"containers\": $(curl -s "${CANNERY_URL}/api/containers" \ -H "Authorization: Bearer $AUTH_TOKEN" | jq . 2>/dev/null || echo '[]')," echo " \"ammo_types\": $(curl -s "${CANNERY_URL}/api/ammo-types" \ -H "Authorization: Bearer $AUTH_TOKEN" | jq . 2>/dev/null || echo '[]')" echo "}" } > "$output_file"
echo -e "${GREEN}✓ Data exported to $output_file${NC}"}
# Backup databasebackup_database() { local output_file=${1:-cannery_backup_$(date +%s).sql}
echo -e "${YELLOW}Backing up Cannery database...${NC}"
pg_dump -h localhost -U postgres cannery > "$output_file" && \ echo -e "${GREEN}✓ Database backed up to $output_file${NC}" || \ echo -e "${RED}✗ Backup failed${NC}"}
# Restore databaserestore_database() { local backup_file=$1
if [ -z "$backup_file" ] || [ ! -f "$backup_file" ]; then echo -e "${RED}Usage: restore_database <backup_file>${NC}" return 1 fi
echo -e "${YELLOW}Restoring Cannery database from $backup_file...${NC}"
psql -h localhost -U postgres cannery < "$backup_file" && \ echo -e "${GREEN}✓ Database restored${NC}" || \ echo -e "${RED}✗ Restore failed${NC}"}
# Main menucase "${1:-help}" in status) check_status ;; create) create_container "$2" "$3" ;; list) list_containers ;; get) get_container "$2" ;; invite) generate_invite ;; export) export_data "${2:-cannery_export.json}" ;; backup) backup_database "${2:-cannery_backup_$(date +%s).sql}" ;; restore) restore_database "$2" ;; *) echo -e "${YELLOW}Cannery Management Tool${NC}" echo "Usage: $0 {status|create|list|get|invite|export|backup|restore} [args]" echo "" echo "Commands:" echo " status - Check instance status" echo " create <name> [tags] - Create new container" echo " list - List all containers" echo " get <id> - Get container details" echo " invite - Generate user invite token" echo " export [file] - Export data to JSON" echo " backup [file] - Backup database" echo " restore <file> - Restore from backup" ;;esacShell - Cannery Initialization and Monitoring
#!/bin/sh
# Cannery Initialization and Monitoring Script# Initialize a fresh Cannery instance and monitor its status
CANNERY_CONTAINER="cannery"CANNERY_DB="cannery-db"CANNERY_PORT=4000CANNERY_URL="http://localhost:${CANNERY_PORT}"
# Initialize applicationinit_cannery() { echo "Initializing Cannery application..."
echo "Step 1: Generating SECRET_KEY_BASE..." SECRET=$(docker run --rm shibaobun/cannery /app/priv/random.sh) echo "SECRET_KEY_BASE=$SECRET"
echo "" echo "Step 2: Waiting for database..." sleep 5
echo "Step 3: Running database migrations..." docker exec $CANNERY_CONTAINER /app/bin/cannery migrate || \ echo "Migrations may have already run"
echo "" echo "Initialization complete!" echo "Access Cannery at: $CANNERY_URL" echo "First user created will be admin"}
# Monitor container healthmonitor_health() { echo "Monitoring Cannery health..."
while true; do clear echo "=== Cannery Health Monitor ===" echo "Time: $(date)" echo ""
# Check container status if docker ps | grep -q $CANNERY_CONTAINER; then echo "✓ Container running" else echo "✗ Container stopped" fi
# Check web interface if curl -s "$CANNERY_URL" > /dev/null 2>&1; then echo "✓ Web interface responding" else echo "✗ Web interface not responding" fi
# Check database if docker exec $CANNERY_DB pg_isready > /dev/null 2>&1; then echo "✓ Database ready" else echo "✗ Database not responding" fi
# Show container stats echo "" echo "Container Stats:" docker stats --no-stream $CANNERY_CONTAINER | tail -1
echo "" echo "Recent logs:" docker logs --tail 5 $CANNERY_CONTAINER
sleep 10 done}
# Perform database maintenancemaintain_database() { echo "Running database maintenance..."
echo "Analyzing tables..." docker exec $CANNERY_DB psql -U postgres -d cannery -c "ANALYZE;" && \ echo "✓ Analysis complete"
echo "" echo "Vacuuming tables..." docker exec $CANNERY_DB psql -U postgres -d cannery -c "VACUUM ANALYZE;" && \ echo "✓ Vacuum complete"
echo "" echo "Checking table sizes..." docker exec $CANNERY_DB psql -U postgres -d cannery -c \ "SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema') ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;" && \ echo "✓ Size report complete"}
# Generate database backupbackup_cannery() { local timestamp=$(date +%Y%m%d_%H%M%S) local backup_file="cannery_backup_${timestamp}.sql"
echo "Creating database backup..."
docker exec $CANNERY_DB pg_dump -U postgres cannery > "$backup_file" && \ echo "✓ Backup complete: $backup_file" || \ echo "✗ Backup failed"}
# View application logsview_logs() { local lines=${1:-50}
echo "Displaying last $lines lines of logs..." docker logs --tail $lines $CANNERY_CONTAINER}
# Main functioncase "${1:-help}" in init) init_cannery ;; monitor) monitor_health ;; maintain) maintain_database ;; backup) backup_cannery ;; logs) view_logs "${2:-50}" ;; *) echo "Cannery Initialization and Monitoring" echo "Usage: $0 {init|monitor|maintain|backup|logs} [args]" echo "" echo "Commands:" echo " init - Initialize fresh Cannery instance" echo " monitor - Monitor instance health and status" echo " maintain - Run database maintenance tasks" echo " backup - Create database backup" echo " logs [lines] - View application logs (default: 50 lines)" ;;esacBest Practices
- Strong Secrets: Generate a strong SECRET_KEY_BASE and store it securely as an environment variable
- Email Configuration: Test SMTP configuration before relying on invitations
- Backup Strategy: Regular database backups are essential for data protection
- Access Control: Use invite-only registration to control who can access your instance
- Secure Hosting: Use HTTPS with proper SSL certificates for your domain
- Database Optimization: Regularly vacuum and analyze your PostgreSQL database
- User Management: Review and manage user access regularly
- Update Schedule: Keep Cannery updated to receive security patches and features
- Monitoring: Set up alerts for database and application health
- Data Privacy: Review privacy implications of storing ammunition records
- Network Security: Consider firewall rules and network segmentation
- Locale Selection: Choose appropriate locale for your user base
Troubleshooting
Issue: “Can’t connect to database” errors
- Verify PostgreSQL is running and accessible
- Check DATABASE_URL environment variable is correct
- Ensure database credentials are accurate
- Check that database port (usually 5432) is accessible
Issue: Emails not sending
- Verify SMTP credentials are correct
- Test SMTP connection with telnet or similar tool
- Check SMTP_HOST and SMTP_PORT are correct
- Ensure email provider allows app passwords
- Review application logs for SMTP errors
Issue: Page shows “internal error”
- Check application logs for error messages
- Verify SECRET_KEY_BASE is set correctly
- Ensure database is running and accessible
- Check that all required environment variables are set
Issue: Can’t create first admin user
- Clear browser cookies and retry
- Verify registration is enabled (REGISTRATION setting)
- Check application logs for registration errors
- Ensure database migrations have completed
Issue: Application slow or unresponsive
- Run database maintenance (VACUUM ANALYZE)
- Check database connection pool size
- Monitor CPU and memory usage
- Review application logs for bottlenecks
- Consider increasing POOL_SIZE if many concurrent users
Issue: Container won’t start
- Check SECRET_KEY_BASE is set and valid
- Verify Docker image built successfully
- Check entrypoint script has execute permissions
- Review Docker logs for startup errors
- Ensure all required dependencies are installed
Update Instructions
To update Cannery to the latest version:
-
Pull Latest Changes: Update your repository with the latest Cannery code
Terminal window git pull origin stable -
Test Locally: Build and test locally with docker-compose first
Terminal window docker-compose builddocker-compose up -
Backup Database: Always backup before updating
Terminal window pg_dump cannery > cannery_backup_pre_update.sql -
Commit Changes: Push updated Dockerfile to GitHub
Terminal window git add Dockerfilegit commit -m "Update Cannery to latest version"git push origin main -
Redeploy: In Klutch.sh dashboard, trigger a manual redeploy
- The new Docker image will be built automatically
- Database migrations will run during startup
-
Verify: Test all functionality after update completes
Use Cases
- Personal Ammo Tracking: Track your ammunition inventory at home
- Range Management: Manage ammunition for multiple shooting sessions
- Collector Documentation: Document firearm and ammunition collections
- Usage Monitoring: Track ammunition consumption over time
- Multi-User Sharing: Share ammunition tracking with trusted users
- Preppers and Survivalists: Maintain detailed inventory records
- Small Gun Clubs: Manage club ammunition inventory
- Training Programs: Track ammunition use in training sessions
- Data Privacy: Keep sensitive ammunition data self-hosted
- Offline Access: Access your data without cloud dependencies
Additional Resources
- Cannery Website - Official project website
- Gitea Repository - Main repository and documentation
- GitHub Mirror - GitHub source mirror
- Weblate Translation - Translation contributions
- Phoenix Framework Docs - Underlying framework documentation
- PostgreSQL Documentation - Database documentation
- Elixir Language Guide - Elixir programming resources
- Docker Documentation - Container deployment resources
- Nginx Proxy Manager - Reverse proxy for Cannery
- Let’s Encrypt - Free SSL certificates
Conclusion
Deploying Cannery on Klutch.sh provides a private, self-hosted ammunition and firearm tracker that keeps your inventory data secure and under your control. By following this guide, you’ve set up a production-ready Cannery instance with persistent database storage for long-term data retention.
Your Cannery deployment is now ready to manage your ammunition inventory with ease. Whether you’re tracking ammo for range days, documenting your collection, or managing inventory across multiple users, Cannery provides a secure, privacy-focused solution for ammunition management.
For additional configuration options, troubleshooting, or advanced setups, refer to the official Cannery documentation and Gitea repository. Regular backups and updates will keep your Cannery instance secure and running smoothly.