Your storage. Your rules.

S3-compatible object storage on your own VPS — with automated backups, a web panel, a CLI, and a full REST API. Powered by Garage, secured by Caddy.

Features

Everything you need,
nothing you don't.

Production-ready object storage with batteries included. No vendor lock-in, no hidden costs.

01 S3-compatible Powered by Garage — works with any AWS SDK, the aws CLI, or S3 client. Storage
02 One-command install Provisions Garage, Let's Encrypt TLS via Caddy, rclone, and nightly cron in one run. Ops
03 Automated backups Nightly SFTP sync with date-based retention. Optional S3-to-S3 replication. Backup
04 Web panel + 2FA Optional Laravel dashboard for buckets, objects, and keys — TOTP and SMTP reset. UI
05 Panel REST API 11 JSON endpoints, one-call tenant provisioning, Swagger UI, and OpenAPI spec. API
06 Self-updating Nightly cron pulls the latest release, runs migrations, and refreshes the panel. Ops
Install

Up and running in minutes.

Clone outside /root so the Laravel panel can run as www-data. Ubuntu 24.04 LTS.

01

Clone the repository

Pick a path like /opt/johnny.

bash
sudo git clone https://github.com/andreapollastri/johnny /opt/johnny
02

Run the autoinstaller

Interactive wizard for Garage, Caddy, rclone, cron, and the optional panel.

bash
cd /opt/johnny
sudo bash scripts/autoinstall.sh
03

Load credentials and go

Use any AWS SDK or S3 client against your endpoint.

bash
source /etc/johnny/credentials/default-s3.env
aws s3 ls --endpoint-url "$AWS_ENDPOINT_URL"
Web panel

A panel you'll actually use.

Monitor storage, manage buckets and keys, issue API tokens — light or dark, secured with TOTP.

s3.your-site.test
Johnny web panel — storage dashboard

Dashboard — storage overview & top buckets

Docs

From zero to objects on your VPS.

Install, operate, and integrate Johnny with the CLI, panel, and REST API.

Overview

Johnny wraps Garage with production-ready automation: TLS via Caddy, nightly SFTP backups with retention, a johnny CLI, and an optional Laravel web panel with two-factor authentication.

Orchestration runs only on your primary VPS (push model). Backup servers only need SSH/SFTP and disk space.

Requirements

  • Ubuntu 24.04 LTS on the primary VPS
  • DNS A/AAAA pointing your hostname at the server before install (Let's Encrypt)
  • For each backup target: SSH/SFTP reachable from the primary, with write access under the remote path

Installation

Prefer the autoinstaller. It installs dependencies, starts Garage, creates the default bucket and keys, configures Caddy TLS, writes /etc/johnny/backup.json, installs nightly cron, and can optionally install the panel.

bash
sudo mkdir -p /opt && sudo git clone https://github.com/andreapollastri/johnny /opt/johnny
cd /opt/johnny
sudo bash scripts/autoinstall.sh

Manual install

bash
sudo bash scripts/install.sh
sudo systemctl start johnny-garage
sudo bash scripts/bootstrap-single-node.sh

Then configure TLS yourself — see config/caddy-johnny.caddy.example or config/nginx-johnny-s3.conf.example.

Web panel

Optional Laravel dashboard for buckets, objects, and Garage API keys. If you skipped it during autoinstall:

bash
sudo bash scripts/install-panel.sh /opt/johnny https://panel.example.com

Create an admin

bash
sudo -u www-data php /opt/johnny/panel/artisan johnny:admin you@example.com 'strong-password'

Open the panel hostname, sign in, then enable TOTP 2FA under Settings.

SMTP for password reset

Default MAIL_MAILER=log only writes to the Laravel log. Set SMTP in panel/.env, then rebuild the config cache:

bash
sudo -u www-data php /opt/johnny/panel/artisan config:cache

CLI

Command Description
johnny version Print installed version
johnny status Garage cluster status
johnny update [--pull] Update installation
johnny bucket list|create List or create buckets
johnny key list|create List or create API keys
johnny backup list|create|delete|run Manage SFTP backup targets
johnny backup set-retention N Keep dated folders for N days (default 90)
Prefer sudo -u johnny johnny … for key and bucket mutations that write Garage state.

Backups

Nightly job (03:00) uses rclone to sync every bucket to one or more SFTP targets under a dated folder layout. Retention deletes YYYY-MM-DD folders older than retention_days.

layout
johnny-backups/
  2026-04-03/
    default/
    my-bucket/
  2026-04-04/
    default/
    my-bucket/

Config: /etc/johnny/backup.json (mode 600). Logs: /var/log/johnny-nightly.log.

S3-to-S3 replication

bash
cp config/replication/media-to-eu.env.example config/replication/media-to-eu.env
# edit credentials, then:
sudo bash scripts/replicate-run.sh config/replication/media-to-eu.env

Panel API

Secured with Laravel Sanctum personal access tokens. Interactive docs at /api/docs, OpenAPI at /api/openapi.yaml.

bash — create token
sudo -u www-data php /opt/johnny/panel/artisan johnny:api-token you@example.com --name=provisioning

Provision bucket + key

bash
curl -sS -X POST \
  "https://panel.example.com/api/buckets/provision" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"bucket":"customer-orders"}'
Method Path Description
GET /api/buckets List buckets
POST /api/buckets Create a bucket
GET /api/buckets/{bucket} Metadata & keys
DEL /api/buckets/{bucket} Delete empty bucket
GET / POST / DEL /api/keys List, create, lookup, delete keys
POST / DEL /api/buckets/{b}/keys… Grant or revoke key access
POST /api/buckets/provision Bucket + key + credentials in one call

Laravel disk

Compatible with Laravel's S3 filesystem driver. Install the Flysystem adapter, paste credentials into .env, and store files.

bash
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
.env
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=GK....123
AWS_SECRET_ACCESS_KEY=012...XYZ
AWS_DEFAULT_REGION=johnny
AWS_BUCKET=my-bucket
AWS_ENDPOINT=https://s3.my-domain.com
AWS_USE_PATH_STYLE_ENDPOINT=true
php
Storage::disk('s3')->put('avatars/photo.jpg', $contents);

The provisioning API returns the same keys in an env map for automated tenant onboarding.

Updates

Repo path is saved in /etc/johnny/repo.path. Nightly cron runs johnny update --pull at 02:30.

bash
sudo johnny update --pull

Pulls the latest release, syncs scripts, runs Composer + migrations + cache rebuild when the panel is present, and applies numbered scripts from scripts/migrations/. Log: /var/log/johnny-update.log.

Security

  • Restrict /etc/johnny (especially backup.json and credentials/) — defaults are already 600/700
  • Prefer SSH keys on backup servers; password auth is documented for simplicity
  • Firewall: expose 443 (and 80 for ACME); restrict 22 to trusted IPs
  • rclone sync can delete extras under each dated prefix — review the rclone sync docs

Configuration

Path Purpose
/etc/johnny/garage.toml Garage configuration
/etc/johnny/backup.json Backup targets and retention
/etc/johnny/credentials/default-s3.env App S3 credentials
/etc/johnny/credentials/backup-internal-s3.env Internal backup S3 credentials
/etc/johnny/repo.path Path to the Johnny repo
/etc/cron.d/johnny-nightly Update + backup schedule
panel/.env Laravel panel environment

Source and issues: github.com/andreapollastri/johnny · MIT License · v1.0.5