# Portainer stack: registry-based images (no build). Use with CI/CD webhook redeploy. # Set in Portainer stack env (or .env): REGISTRY, IMAGE_TAG (defaults below). # Images: ${REGISTRY}/mifi-holdings/shorty-qr-api:${IMAGE_TAG}, shorty-qr-web:${IMAGE_TAG} services: kutt_db: image: postgres:16-alpine container_name: mifi-shorty_kutt_db restart: unless-stopped networks: - backend volumes: - /mnt/config/docker/kutt/postgres:/var/lib/postgresql/data environment: POSTGRES_USER: ${DB_USER:-kutt} POSTGRES_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD} POSTGRES_DB: ${DB_NAME:-kutt} healthcheck: test: [ 'CMD-SHELL', 'pg_isready -U ${DB_USER:-kutt} -d ${DB_NAME:-kutt}', ] interval: 10s timeout: 5s retries: 5 kutt_redis: image: redis:8-alpine container_name: mifi-shorty_kutt_redis restart: unless-stopped networks: - backend volumes: - /mnt/config/docker/kutt/redis:/data command: redis-server --appendonly yes healthcheck: test: ['CMD', 'redis-cli', 'ping'] interval: 10s timeout: 5s retries: 5 kutt: image: kutt/kutt:latest container_name: mifi-shorty_kutt restart: unless-stopped networks: - marina-net - backend depends_on: kutt_db: condition: service_healthy kutt_redis: condition: service_healthy healthcheck: test: - CMD - node - -e - 'require("http").get("http://127.0.0.1:3000/", (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on("error", () => process.exit(1))' interval: 30s timeout: 10s retries: 3 start_period: 40s environment: ADMIN_EMAILS: ${ADMIN_EMAILS:?Set ADMIN_EMAILS} DEFAULT_DOMAIN: mifi.me DB_CLIENT: pg DB_HOST: kutt_db DB_PORT: '5432' DB_USER: ${DB_USER:-kutt} DB_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD} DB_NAME: ${DB_NAME:-kutt} DISALLOW_ANONYMOUS_LINKS: 'true' DISALLOW_REGISTRATION: 'true' LINK_LENGTH: 6 JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET} MAIL_ENABLED: 'true' MAIL_HOST: 'mail.mifi.holdings' MAIL_PORT: '465' MAIL_SECURE: 'true' MAIL_USER: 'mailbot@mifi.ventures' MAIL_FROM: 'mifi Holdings Shorty ' MAIL_PASSWORD: '${SMTP_PASSWORD:?Set SMTP_PASSWORD}' NODE_ENV: production # OIDC_ENABLED: 'true' # OIDC_ISSUER: 'https://git.mifi.dev' # OIDC_CLIENT_ID: '2a6d1ecd-4e2f-42e5-922e-22e878230488' # OIDC_CLIENT_SECRET: '${OIDC_CLIENT_SECRET:?Set OIDC_CLIENT_SECRET}' REDIS_ENABLED: 'true' REDIS_HOST: kutt_redis REDIS_PORT: '6379' SITE_DOMAIN: link.mifi.me SITE_NAME: 'mifi Shorty' labels: - 'traefik.enable=true' - 'docker.network=marina-net' - 'traefik.http.routers.kutt-mifi.rule=Host(`mifi.me`)' - 'traefik.http.routers.kutt-mifi.entrypoints=websecure' - 'traefik.http.routers.kutt-mifi.tls.certresolver=letsencrypt' - 'traefik.http.routers.kutt-mifi.service=kutt-short' - 'traefik.http.services.kutt-short.loadbalancer.server.port=3000' # Backend timeout: use transport from file provider (see traefik-kutt-timeout.example.yml). - 'traefik.http.services.kutt-short.loadbalancer.serversTransport=kutt-long-timeout@file' qr_api: image: ${REGISTRY:-git.mifi.dev}/mifi-holdings/shorty-qr-api:${IMAGE_TAG:-latest} container_name: mifi-shorty_qr_api restart: unless-stopped networks: - backend volumes: - /mnt/config/docker/qr/db:/data - /mnt/config/docker/qr/uploads:/uploads environment: PORT: '8080' DB_PATH: /data/db.sqlite UPLOADS_PATH: /uploads KUTT_API_KEY: ${KUTT_API_KEY:-} KUTT_BASE_URL: http://kutt:3000 SHORT_DOMAIN: https://mifi.me healthcheck: test: - CMD - node - -e - 'require("http").get("http://127.0.0.1:8080/health", (r) => { r.resume(); process.exit(r.statusCode === 200 ? 0 : 1); }).on("error", () => process.exit(1))' interval: 10s timeout: 5s retries: 5 start_period: 10s qr_web: image: ${REGISTRY:-git.mifi.dev}/mifi-holdings/shorty-qr-web:${IMAGE_TAG:-latest} container_name: mifi-shorty_qr_web restart: unless-stopped networks: - marina-net - backend depends_on: qr_api: condition: service_healthy environment: QR_API_URL: http://qr_api:8080 healthcheck: test: - CMD - node - -e - 'require("http").get("http://127.0.0.1:3000/", (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on("error", () => process.exit(1))' interval: 30s timeout: 10s retries: 3 start_period: 15s labels: - 'traefik.enable=true' - 'docker.network=marina-net' - 'traefik.http.routers.qr-web.rule=Host(`qr.mifi.dev`)' - 'traefik.http.routers.qr-web.entrypoints=websecure' - 'traefik.http.routers.qr-web.tls.certresolver=letsencrypt' - 'traefik.http.routers.qr-web.service=qr-web' - 'traefik.http.routers.qr-web.middlewares=qr-web-basicauth' - 'traefik.http.middlewares.qr-web-basicauth.basicauth.users=mifi:$$apr1$$9fgAWvE1$$bLGgUtpFjdaexkV5gooWq.' - 'traefik.http.services.qr-web.loadbalancer.server.port=3000' networks: marina-net: external: true backend: driver: bridge