Skip to content

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

  1. Create a Dockerfile

    Create a Dockerfile in the root of your repository to containerize Cannery:

    FROM elixir:1.16-alpine as builder
    # Install build dependencies
    RUN apk add --no-cache \
    build-base \
    git \
    nodejs \
    npm \
    postgresql-client
    # Set working directory
    WORKDIR /app
    # Clone Cannery repository
    RUN git clone https://gitea.bubbletea.dev/shibao/cannery.git .
    # Install dependencies
    RUN mix deps.get --only prod
    RUN npm install --prefix assets
    RUN npm run deploy --prefix assets
    # Build release
    RUN mix phx.digest
    RUN mix release
    # Runtime stage
    FROM alpine:latest
    # Install runtime dependencies
    RUN apk add --no-cache \
    openssl \
    postgresql-client \
    bash
    # Create app user
    RUN addgroup -g 1000 app && \
    adduser -D -u 1000 -G app app
    # Copy release from builder
    COPY --from=builder /app/_build/prod/rel/cannery /app
    RUN chown -R app:app /app
    # Set working directory
    WORKDIR /app
    # Switch to app user
    USER app
    # Expose port
    EXPOSE 4000
    # Start command
    CMD ["/app/bin/cannery", "start"]
  2. Create Environment Configuration File

    Create a .env.example file to document required environment variables:

    # Cannery Environment Configuration
    # Application Host and Port
    HOST=example-app.klutch.sh
    PORT=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 Configuration
    DATABASE_URL=ecto://postgres:postgres@localhost/cannery
    POOL_SIZE=10
    ECTO_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.com
    SMTP_PORT=587
    SMTP_USERNAME=your-email@gmail.com
    SMTP_PASSWORD=your-app-password
    SMTP_SSL=false
    # Email Settings
    EMAIL_FROM=no-reply@example-app.klutch.sh
    EMAIL_NAME=Cannery
    # Optional: Environment mode
    MIX_ENV=prod
  3. Create Configuration Directory

    Create a config directory with Cannery configuration files for production:

    Create config/prod.exs:

    import Config
    # Configure your database
    config :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 Mailer
    config :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 configuration
    config :cannery,
    registration: System.get_env("REGISTRATION") || "invite",
    locale: System.get_env("LOCALE") || "en_US",
    email_from: System.get_env("EMAIL_FROM") || "no-reply@localhost"
  4. Create Entrypoint Script

    Create an entrypoint.sh file to initialize the application:

    #!/bin/bash
    set -e
    echo "Starting Cannery..."
    # Generate SECRET_KEY_BASE if not provided
    if [ -z "$SECRET_KEY_BASE" ]; then
    echo "Generating SECRET_KEY_BASE..."
    export SECRET_KEY_BASE=$(openssl rand -base64 48)
    fi
    # Wait for database to be ready
    echo "Waiting for database..."
    while ! nc -z $DATABASE_HOST 5432 2>/dev/null; do
    sleep 1
    done
    # Run migrations
    echo "Running database migrations..."
    /app/bin/cannery migrate || true
    # Start application
    echo "Starting Cannery application..."
    exec /app/bin/cannery start

    Make it executable:

    Terminal window
    chmod +x entrypoint.sh
  5. Create .gitignore File

    Create a .gitignore file to exclude sensitive data from version control:

    # Environment and secrets
    .env
    .env.local
    .env.*.local
    SECRET_KEY_BASE
    # Elixir/Mix
    _build/
    deps/
    *.beam
    erl_crash.dump
    .fetch
    # Node
    node_modules/
    assets/node_modules/
    # Database
    *.sqlite
    *.sqlite3
    # IDE
    .vscode/
    .idea/
    *.swp
    *.swo
    *~
    .DS_Store
    # Generated files
    priv/static/
    cover/
    doc/
    .elixir_ls/
    # Logs
    *.log
  6. Create docker-compose.yml for Local Testing

    Create a docker-compose.yml file for local development and testing (not for Klutch.sh deployment):

    version: '3.8'
    services:
    cannery:
    build: .
    container_name: cannery-dev
    ports:
    - "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=public
    depends_on:
    - cannery-db
    networks:
    - cannery-network
    cannery-db:
    image: postgres:16-alpine
    container_name: cannery-db
    environment:
    - POSTGRES_USER=postgres
    - POSTGRES_PASSWORD=postgres
    - POSTGRES_DB=cannery
    volumes:
    - cannery_db_data:/var/lib/postgresql/data
    networks:
    - cannery-network
    volumes:
    cannery_db_data:
    networks:
    cannery-network:
    driver: bridge
  7. Push 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 .gitignore
    git commit -m "Initial Cannery deployment configuration"
    git push origin main
  8. Deploy on Klutch.sh

    1. Navigate to klutch.sh/app and log in to your dashboard
    2. Click Create New App
    3. Select Docker as the deployment method (detected automatically from your Dockerfile)
    4. Connect your GitHub repository containing the Cannery deployment files
    5. Confirm the Dockerfile is detected from your root directory
    6. Click Deploy to start the deployment process
    7. Wait for the deployment to complete and your app to become active
  9. Configure Traffic Rules and Persistent Storage

    1. In your app dashboard, navigate to Traffic settings
    2. Select HTTP as the traffic type
    3. Set the internal port to 4000 (Cannery Phoenix server default)
    4. Save your traffic configuration
    5. For database persistence, navigate to Volumes settings
    6. Click Add Volume
    7. Enter mount path: /var/lib/postgresql/data
    8. Set volume size to 50GB (adjust based on expected number of ammunition records)
    9. Click Attach to create and mount the persistent volume
    10. 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

  1. Navigate to http://example-app.klutch.sh in your browser
  2. You’ll see the Cannery login page
  3. Click Register to create your first account
  4. The first user created will automatically be promoted to admin
  5. Log in with your new credentials

Configure SMTP Email

Cannery requires SMTP configuration for sending user invitations:

  1. Log in as admin
  2. Navigate to Settings or Admin Settings
  3. 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
  4. Test the configuration by sending a test email
  5. Save your SMTP settings

Set Registration Mode

  1. Navigate to admin settings
  2. Choose registration mode:
    • Invite Only (default): Users can only join with an invite token
    • Public: Users can self-register without an invite
  3. Save your preference

Create Your First Container

  1. Navigate to the Containers section
  2. Click New Container
  3. Enter a name (e.g., “Home Safe”, “Range Bag”, “Storage Box”)
  4. Add optional tags for organization
  5. Click Create

Add Ammunition Types

  1. Go to Ammunition Types section
  2. Click New Type
  3. Enter ammunition specifications:
    • Caliber/Type name
    • Cartridge type
    • Grain weight (if applicable)
  4. Save the ammunition type

Add Ammo Packs to Containers

  1. Select a container
  2. Click Add Ammo Pack
  3. Select the ammunition type
  4. Enter the quantity
  5. Add notes or date information
  6. Click Add

Create User Invitations

  1. Navigate to Users or Admin section
  2. Click Generate Invite Token
  3. Share the invite link with users
  4. Users can register using the invite token
  5. Manage user access from admin settings

Track Ammo Usage

  1. Before a range day, go to your containers
  2. Select ammo packs to stage for shooting
  3. Click Stage for Range Day
  4. After range day, create shot records
  5. Record ammunition used and shots fired
  6. Track your ammunition consumption over time

Backup Your Data

  1. Use PostgreSQL tools to backup your database:
    Terminal window
    pg_dump cannery > cannery_backup.sql
  2. Store backups securely
  3. 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 instance
  • PORT=4000 - Internal port Cannery listens on
  • SECRET_KEY_BASE - Generated secret key for session encryption (must be set)
  • REGISTRATION=invite - Registration mode (invite or public)
  • SMTP_HOST - SMTP server for email
  • SMTP_PORT=587 - SMTP port (typically 587 for TLS)
  • SMTP_USERNAME - Email account username
  • SMTP_PASSWORD - Email account password
  • EMAIL_FROM - Sender address for emails
  • LOCALE=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 Configuration
HOST=example-app.klutch.sh
PORT=4000
MIX_ENV=prod
# Generate with: docker run -it shibaobun/cannery /app/priv/random.sh
SECRET_KEY_BASE=your-very-long-random-key-base-here
# Database Configuration
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=cannery
DATABASE_USER=postgres
DATABASE_PASSWORD=secure-password-here
POOL_SIZE=10
ECTO_IPV6=false
# SMTP Configuration (Email)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-specific-password
SMTP_SSL=false
# Email Settings
EMAIL_FROM=noreply@example-app.klutch.sh
EMAIL_NAME=Cannery Ammunition Tracker
# Registration and Localization
REGISTRATION=invite
LOCALE=en_US
# Performance
LOG_LEVEL=notice
PREFERRED_CLI_ENV=prod

Code Examples

Elixir - Cannery Container Management

# Cannery Container Management Module
defmodule 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))
end
end

Bash - 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 output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Check if instance is online
check_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 container
create_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 containers
list_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 details
get_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 token
generate_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 data
export_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 database
backup_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 database
restore_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 menu
case "${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"
;;
esac

Shell - 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=4000
CANNERY_URL="http://localhost:${CANNERY_PORT}"
# Initialize application
init_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 health
monitor_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 maintenance
maintain_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 backup
backup_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 logs
view_logs() {
local lines=${1:-50}
echo "Displaying last $lines lines of logs..."
docker logs --tail $lines $CANNERY_CONTAINER
}
# Main function
case "${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)"
;;
esac

Best 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:

  1. Pull Latest Changes: Update your repository with the latest Cannery code

    Terminal window
    git pull origin stable
  2. Test Locally: Build and test locally with docker-compose first

    Terminal window
    docker-compose build
    docker-compose up
  3. Backup Database: Always backup before updating

    Terminal window
    pg_dump cannery > cannery_backup_pre_update.sql
  4. Commit Changes: Push updated Dockerfile to GitHub

    Terminal window
    git add Dockerfile
    git commit -m "Update Cannery to latest version"
    git push origin main
  5. Redeploy: In Klutch.sh dashboard, trigger a manual redeploy

    • The new Docker image will be built automatically
    • Database migrations will run during startup
  6. 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

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.