# Installation Guide Comprehensive installation guide for MyGarage covering Docker Compose, Docker Run, bare metal installation, and NAS deployments. ## Table of Contents - [Docker Compose (Recommended)](#docker-compose-recommended) - [Time Zone](#time-zone) - [Running as a Different User](#running-as-a-different-user) - [Upload Limits](#upload-limits) - [Docker Run](#docker-run) - [Bare Metal Installation](#bare-metal-installation) - [Unraid Installation](#unraid-installation) - [Synology NAS Installation](#synology-nas-installation) - [PostgreSQL Setup](#postgresql-setup) - [Reverse Proxy Configuration](#reverse-proxy-configuration) --- ## Docker Compose (Recommended) ### Basic Setup Create `docker-compose.yml`: ```yaml services: mygarage: image: ghcr.io/homelabforge/mygarage:latest container_name: mygarage ports: - "8686:8686" volumes: - ./data:/data environment: - TZ=America/Chicago # your household's IANA time zone (see Time Zone below) restart: unless-stopped ``` Prepare the data directory and start: ```bash mkdir -p ./data sudo chown -R 1000:1000 ./data docker-compose up -d ``` > **Note:** The container runs as a non-root user, UID 1000 by default. The data directory must be owned by whichever UID the container runs as, or the application will fail with permission errors. See [Troubleshooting - Permission Denied](Troubleshooting#permission-denied-errors) for details. ### Running as a Different User If UID 1000 is taken on your host, or you want MyGarage under its own account, set `user:` to that account's UID and GID: ```yaml services: mygarage: image: ghcr.io/homelabforge/mygarage:latest user: "1001:1001" volumes: - ./data:/data ``` Then give that account the data directory: ```bash sudo chown -R 1001:1001 ./data ``` MyGarage only writes to `/data` and `/tmp`, so any UID works as long as it owns `./data`. The account doesn't need to exist inside the container. If you're moving an existing install from another host, the copied files keep their old owner, so run the `chown` after copying. With `docker run`, use `--user 1001:1001`. ### With PostgreSQL For multi-user deployments or better performance: ```yaml services: postgres: image: postgres:16-alpine container_name: mygarage-postgres environment: POSTGRES_DB: mygarage POSTGRES_USER: mygarage POSTGRES_PASSWORD: CHANGEME_SECURE_PASSWORD volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped mygarage: image: ghcr.io/homelabforge/mygarage:latest container_name: mygarage depends_on: - postgres ports: - "8686:8686" volumes: - ./data:/data environment: - MYGARAGE_DATABASE_URL=postgresql+asyncpg://mygarage:CHANGEME_SECURE_PASSWORD@postgres:5432/mygarage restart: unless-stopped volumes: postgres_data: ``` --- ### Time Zone MyGarage computes every calendar date (due dates, overdue badges, notifications, the dates forms fill in) in one household time zone, resolved in this order: 1. **Settings -> System -> Timezone** (recommended; no restart needed) 2. `MYGARAGE_TIMEZONE` environment variable 3. The container's zone (`TZ`, as in the compose file above) 4. UTC Without any of these, a default install computes dates in UTC, which shifts every "today" for households west of Greenwich during the evening. Log timestamps always stay in UTC. Scheduled jobs compute their *dates* in the household zone, but their *trigger times* follow the container's zone. ### Upload Limits Upload size caps are set in the environment, not in Settings: | Variable | Default | Applies to | |----------|---------|------------| | `MYGARAGE_MAX_UPLOAD_SIZE_MB` | `10` | Photos, attachments and receipt photos | | `MYGARAGE_MAX_DOCUMENT_SIZE_MB` | `25` | Documents | Change them and restart MyGarage. **Settings → File Management** shows the limits in force, read-only: the size cap and allowed file types for photos, attachments and documents, and the storage directories. A window sticker upload is capped at 10 MB whatever these say. ## Docker Run For quick testing or simple deployments: ```bash # Pre-create the data directory with correct ownership (container runs as UID 1000; # to use another UID, add --user UID:GID and chown to that instead) mkdir -p ./data sudo chown -R 1000:1000 ./data docker run -d \ --name mygarage \ -p 8686:8686 \ -v $(pwd)/data:/data \ --restart unless-stopped \ ghcr.io/homelabforge/mygarage:latest ``` With PostgreSQL: ```bash docker run -d \ --name mygarage \ -p 8686:8686 \ -v $(pwd)/data:/data \ -e MYGARAGE_DATABASE_URL=postgresql+asyncpg://user:password@postgres-host:5432/mygarage \ --restart unless-stopped \ ghcr.io/homelabforge/mygarage:latest ``` Replace `user`, `password`, and `postgres-host` with your PostgreSQL credentials. --- ## Bare Metal Installation ### Prerequisites - Python 3.14 or higher - Bun 1.4.2 or higher ([Install guide](https://bun.sh/docs/installation)) - Git ### Backend Setup ```bash # Clone repository git clone https://github.com/homelabforge/mygarage.git cd mygarage/backend # Install dependencies with uv (https://docs.astral.sh/uv/) uv sync # creates .venv from pyproject.toml + uv.lock # Run the app (auto-runs migrations on first start) uv run granian --interface asgi --host 0.0.0.0 --port 8686 --workers 1 app.main:app ``` ### Frontend Setup ```bash # In a new terminal cd mygarage/frontend # Install dependencies bun install # Build for production bun run build # Or run development server bun dev ``` ### Running in Production For bare metal production deployments, use a process manager like **systemd** or **supervisor**. **Example systemd service** (`/etc/systemd/system/mygarage.service`): ```ini [Unit] Description=MyGarage Vehicle Tracker After=network.target [Service] Type=simple User=mygarage WorkingDirectory=/opt/mygarage/backend Environment="PATH=/opt/mygarage/backend/.venv/bin" ExecStart=/opt/mygarage/backend/.venv/bin/granian --interface asgi --host 0.0.0.0 --port 8686 --workers 1 app.main:app Restart=always [Install] WantedBy=multi-user.target ``` Enable and start: ```bash sudo systemctl enable mygarage sudo systemctl start mygarage ``` --- ## Unraid Installation There is no Community Applications template for MyGarage. Add the container by hand: 1. Go to **Docker** tab 2. Click **Add Container** 3. Configure: - **Name**: MyGarage - **Repository**: `ghcr.io/homelabforge/mygarage:latest` - **Network Type**: Bridge - **Port**: 8686 → 8686 - **Path**: `/data` → `/mnt/user/appdata/mygarage` 4. Click **Apply** --- ## Synology NAS Installation ### Using Container Manager (DSM 7.x) 1. Open **Container Manager** 2. Go to **Registry** and search for "mygarage" 3. Download `ghcr.io/homelabforge/mygarage` 4. Go to **Container** → **Create** 5. Select the mygarage image 6. Configure: - **Container Name**: mygarage - **Port**: Local 8686 → Container 8686 - **Volume**: Create folder `/docker/mygarage/data` → Mount to `/data` 7. Click **Apply** ### Using Docker Compose (DSM 7.2+) 1. Create folder: `/docker/mygarage` 2. Create `docker-compose.yml` (see Docker Compose section above) 3. Open Terminal via SSH 4. Navigate to folder and run: ```bash cd /volume1/docker/mygarage sudo docker-compose up -d ``` --- ## PostgreSQL Setup ### Why PostgreSQL? - **Multi-user deployments**: Better concurrent access - **Performance**: Faster queries with many records - **Reliability**: Better data integrity and backup options - **Scalability**: Handles thousands of service records ### Connection String Format ``` postgresql+asyncpg://username:password@hostname:port/database ``` ### Environment Variable ```bash MYGARAGE_DATABASE_URL=postgresql+asyncpg://mygarage:PASSWORD@localhost:5432/mygarage ``` ### Migration from SQLite MyGarage has no built-in way to move data from SQLite to PostgreSQL: a full backup restores only into the same kind of database. See [Migrating from SQLite to PostgreSQL](Database-Configuration.md#migrating-from-sqlite-to-postgresql). --- ## Reverse Proxy Configuration Behind a proxy, also set `MYGARAGE_TRUSTED_PROXIES` so rate limits and audit logs see real clients, not the proxy: see [Client IP and Rate Limits](Reverse-Proxy#client-ip-and-rate-limits). ### Traefik (Docker Labels) Add to your `docker-compose.yml`: ```yaml labels: - "traefik.enable=true" - "traefik.http.routers.mygarage.rule=Host(`garage.example.com`)" - "traefik.http.routers.mygarage.entrypoints=https" - "traefik.http.routers.mygarage.tls.certresolver=letsencrypt" - "traefik.http.services.mygarage.loadbalancer.server.port=8686" ``` ### Nginx ```nginx server { listen 443 ssl http2; server_name garage.example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://localhost:8686; 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; proxy_set_header X-Forwarded-Host $host; } } ``` ### Caddy ``` garage.example.com { reverse_proxy localhost:8686 } ``` --- ## Background Scheduler MyGarage includes a background scheduler for automated tasks like maintenance reminders, insurance/warranty expiration alerts, NHTSA recall checking, odometer milestone notifications, and DEF low-level alerts. The scheduler is **disabled by default** and must be explicitly enabled. ### Enabling the Scheduler Add the `SCHEDULER_ENABLED` environment variable to your container: ```yaml environment: - SCHEDULER_ENABLED=true ``` Or with `docker run`: ```bash docker run -d \ --name mygarage \ -p 8686:8686 \ -v $(pwd)/data:/data \ -e SCHEDULER_ENABLED=true \ --restart unless-stopped \ ghcr.io/homelabforge/mygarage:latest ``` ### Scheduled Jobs | Job | Schedule | Description | |-----|----------|-------------| | Reminder notifications | Daily 8 AM UTC | Sends a heads-up when a reminder turns due soon, then a daily nag while it's overdue | | Document expiration | Daily 9 AM UTC | Checks insurance and warranty expiration dates | | Odometer milestones | Daily 10 AM UTC | Sends odometer milestone notifications | | DEF low level | Daily 11 AM UTC | Alerts when a diesel vehicle's DEF drops below the configured threshold | | Recall check | Daily 2 AM UTC | Checks active vehicles for new recalls (NHTSA, and RappelConso for vehicles registered in France) once the **Recall Check Interval** (Settings → Integrations) has passed. NHTSA is skipped while NHTSA or automatic recall checking is off | | API limit resets | Daily/Monthly midnight UTC | Resets usage counters for rate-limited APIs | Times are for a container running in UTC. With `TZ` set, jobs trigger at these times in that zone (see [Time Zone](#time-zone)), except the recall check, which always runs at 2 AM UTC. ### Important Notes - The scheduler runs **in-process** using APScheduler. Only **one instance** should have `SCHEDULER_ENABLED=true` at a time. - If running multiple replicas, only enable the scheduler on a single replica. - Telegram fuel commands run in every instance, whatever `SCHEDULER_ENABLED` says. With several replicas they would compete for the bot's messages, so leave **Accept fuel commands** off. - Notification delivery requires at least one notification service configured in Settings (ntfy, Gotify, Pushover, Slack, Discord, Telegram, Matrix, or Email). - Each notification type can be toggled individually in Settings → Notifications. --- ## Health Checks Add health check to Docker Compose: ```yaml healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8686/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s ``` --- ## Next Steps - [First Time Setup](First-Time-Setup) - Add your first vehicle - [Authentication](Authentication) - Configure user accounts - [Database Configuration](Database-Configuration) - Optimize your database - [Reverse Proxy](Reverse-Proxy) - Advanced proxy configuration ## Troubleshooting See the [Troubleshooting Guide](Troubleshooting) for common installation issues.