[{"data":1,"prerenderedAt":415},["ShallowReactive",2],{"blog-guides/fly-io":3},{"id":4,"title":5,"body":6,"category":388,"date":389,"dateModified":389,"description":390,"draft":391,"extension":392,"faq":393,"featured":391,"headerVariant":400,"image":401,"keywords":402,"meta":403,"navigation":404,"ogDescription":405,"ogTitle":401,"path":406,"readTime":401,"schemaOrg":407,"schemaType":408,"seo":409,"sitemap":410,"stem":411,"tags":412,"twitterCard":413,"__hash__":414},"blog/blog/guides/fly-io.md","Fly.io Security Guide for Vibe Coders",{"type":7,"value":8,"toc":362},"minimark",[9,13,17,28,33,36,39,43,46,51,61,82,86,92,96,99,105,109,115,119,122,128,132,135,141,145,148,154,158,164,168,171,177,183,187,190,196,199,205,209,249,253,256,262,266,269,275,309,313,316,336,343],[10,11,5],"h1",{"id":12},"flyio-security-guide-for-vibe-coders",[14,15,16],"p",{},"Published on January 23, 2026 - 11 min read",[18,19,20],"tldr",{},[14,21,22,23,27],{},"Fly.io runs your apps at the edge globally, which creates unique security considerations. Always use ",[24,25,26],"code",{},"fly secrets set"," for sensitive data (never commit to fly.toml), enable private networking between machines, use firewall rules to restrict access, and configure health checks properly. The distributed nature means you need to think about security at every edge location.",[29,30,32],"h2",{"id":31},"why-flyio-security-matters-for-vibe-coding","Why Fly.io Security Matters for Vibe Coding",[14,34,35],{},"Fly.io deploys your applications to edge locations worldwide, running them in Firecracker microVMs. This architecture is fantastic for performance but introduces security considerations that AI coding tools often overlook. When you ask an AI to \"deploy my app to Fly.io,\" it might generate a working configuration but miss critical security hardening.",[14,37,38],{},"The edge deployment model means your app runs in multiple locations simultaneously. A security misconfiguration affects every deployment region, potentially exposing your app across the globe.",[29,40,42],{"id":41},"secrets-management","Secrets Management",[14,44,45],{},"Fly.io provides a built-in secrets manager that injects environment variables at runtime. This is the only safe way to handle sensitive data.",[47,48,50],"h3",{"id":49},"setting-secrets-correctly","Setting Secrets Correctly",[52,53,58],"pre",{"className":54,"code":56,"language":57},[55],"language-text","# Set secrets via CLI (the secure way)\nfly secrets set DATABASE_URL=\"postgres://user:pass@host:5432/db\"\nfly secrets set API_KEY=\"sk-your-secret-key\"\nfly secrets set JWT_SECRET=\"your-256-bit-secret\"\n\n# Set multiple secrets at once\nfly secrets set \\\n  STRIPE_SECRET_KEY=\"sk_live_...\" \\\n  SENDGRID_API_KEY=\"SG....\"\n\n# List secrets (values are hidden)\nfly secrets list\n\n# Unset a secret\nfly secrets unset OLD_API_KEY\n","text",[24,59,56],{"__ignoreMap":60},"",[62,63,64,68],"warning-box",{},[47,65,67],{"id":66},"common-ai-generated-mistake","Common AI-Generated Mistake",[14,69,70,71,74,75,78,79,81],{},"AI tools sometimes put secrets directly in ",[24,72,73],{},"fly.toml"," under ",[24,76,77],{},"[env]",". This exposes your secrets in version control. Always use ",[24,80,26],{}," instead of adding sensitive values to fly.toml.",[47,83,85],{"id":84},"what-goes-where","What Goes Where",[52,87,90],{"className":88,"code":89,"language":57},[55],"# fly.toml - Safe for non-sensitive config\n[env]\n  NODE_ENV = \"production\"\n  LOG_LEVEL = \"info\"\n  PUBLIC_API_URL = \"https://api.example.com\"\n\n# fly secrets - Required for sensitive data\n# DATABASE_URL, API_KEY, JWT_SECRET, etc.\n",[24,91,89],{"__ignoreMap":60},[29,93,95],{"id":94},"private-networking","Private Networking",[14,97,98],{},"Fly.io provides a private IPv6 network (6PN) between your apps. This allows internal services to communicate without exposing endpoints to the internet.",[52,100,103],{"className":101,"code":102,"language":57},[55],"# fly.toml - Configure internal services\n[[services]]\n  internal_port = 8080\n  protocol = \"tcp\"\n\n  # This exposes the app publicly\n  [[services.ports]]\n    port = 443\n    handlers = [\"tls\", \"http\"]\n\n# For internal-only services (databases, workers)\n[[services]]\n  internal_port = 5432\n  protocol = \"tcp\"\n  # No [services.ports] = not publicly accessible\n",[24,104,102],{"__ignoreMap":60},[47,106,108],{"id":107},"connecting-between-apps","Connecting Between Apps",[52,110,113],{"className":111,"code":112,"language":57},[55],"# Access internal services via .flycast or .internal domains\n# From within your Fly app:\nconst dbUrl = \"postgres://user:pass@my-db-app.flycast:5432/db\"\n\n// Or using the internal DNS\nconst redisUrl = \"redis://my-redis.internal:6379\"\n",[24,114,112],{"__ignoreMap":60},[29,116,118],{"id":117},"firewall-and-network-security","Firewall and Network Security",[14,120,121],{},"Configure which connections are allowed using fly.toml and machine-level settings.",[52,123,126],{"className":124,"code":125,"language":57},[55],"# fly.toml - Restrict to specific ports and protocols\n[[services]]\n  internal_port = 8080\n  protocol = \"tcp\"\n  auto_stop_machines = true\n  auto_start_machines = true\n\n  [[services.ports]]\n    port = 80\n    handlers = [\"http\"]\n    force_https = true  # Redirect HTTP to HTTPS\n\n  [[services.ports]]\n    port = 443\n    handlers = [\"tls\", \"http\"]\n\n  [[services.http_checks]]\n    interval = \"10s\"\n    timeout = \"2s\"\n    path = \"/health\"\n",[24,127,125],{"__ignoreMap":60},[47,129,131],{"id":130},"ip-allowlisting","IP Allowlisting",[14,133,134],{},"For admin endpoints or internal tools, restrict access by IP:",[52,136,139],{"className":137,"code":138,"language":57},[55],"// Middleware to check allowed IPs\nconst ALLOWED_IPS = process.env.ALLOWED_IPS?.split(',') || [];\n\nfunction ipAllowlist(req, res, next) {\n  // Fly.io sets the client IP in this header\n  const clientIP = req.headers['fly-client-ip'] ||\n                   req.headers['x-forwarded-for']?.split(',')[0];\n\n  if (ALLOWED_IPS.length > 0 && !ALLOWED_IPS.includes(clientIP)) {\n    return res.status(403).json({ error: 'Forbidden' });\n  }\n\n  next();\n}\n\n// Protect admin routes\napp.use('/admin', ipAllowlist, adminRouter);\n",[24,140,138],{"__ignoreMap":60},[29,142,144],{"id":143},"machine-and-volume-security","Machine and Volume Security",[14,146,147],{},"Fly Machines are the compute units that run your app. Secure their configuration properly.",[52,149,152],{"className":150,"code":151,"language":57},[55],"# fly.toml - Machine configuration\n[build]\n  dockerfile = \"Dockerfile\"\n\n[deploy]\n  release_command = \"npm run migrate\"\n\n[mounts]\n  source = \"data\"\n  destination = \"/data\"\n\n# Set memory and CPU limits\n[[vm]]\n  cpu_kind = \"shared\"\n  cpus = 1\n  memory_mb = 256\n",[24,153,151],{"__ignoreMap":60},[47,155,157],{"id":156},"dockerfile-security","Dockerfile Security",[52,159,162],{"className":160,"code":161,"language":57},[55],"# Use specific versions, not 'latest'\nFROM node:20-alpine\n\n# Create non-root user\nRUN addgroup -S appgroup && adduser -S appuser -G appgroup\n\nWORKDIR /app\n\n# Copy package files first (better caching)\nCOPY package*.json ./\nRUN npm ci --only=production\n\n# Copy application code\nCOPY --chown=appuser:appgroup . .\n\n# Switch to non-root user\nUSER appuser\n\nEXPOSE 8080\nCMD [\"node\", \"server.js\"]\n",[24,163,161],{"__ignoreMap":60},[29,165,167],{"id":166},"health-checks-and-monitoring","Health Checks and Monitoring",[14,169,170],{},"Proper health checks ensure only healthy machines receive traffic, which is also a security measure against compromised instances.",[52,172,175],{"className":173,"code":174,"language":57},[55],"# fly.toml - Comprehensive health checks\n[[services.http_checks]]\n  interval = \"15s\"\n  timeout = \"5s\"\n  grace_period = \"10s\"\n  method = \"GET\"\n  path = \"/health\"\n  protocol = \"http\"\n\n  [services.http_checks.headers]\n    X-Health-Check = \"true\"\n\n[[services.tcp_checks]]\n  interval = \"15s\"\n  timeout = \"2s\"\n  grace_period = \"5s\"\n",[24,176,174],{"__ignoreMap":60},[52,178,181],{"className":179,"code":180,"language":57},[55],"// Health check endpoint\napp.get('/health', (req, res) => {\n  // Verify the request is a genuine health check\n  if (req.headers['x-health-check'] !== 'true') {\n    // Log potential probe attempts\n    console.warn('Health check without header from:', req.ip);\n  }\n\n  // Check critical dependencies\n  const checks = {\n    database: checkDatabaseConnection(),\n    redis: checkRedisConnection(),\n  };\n\n  const allHealthy = Object.values(checks).every(v => v);\n  res.status(allHealthy ? 200 : 503).json({ status: allHealthy ? 'healthy' : 'unhealthy', checks });\n});\n",[24,182,180],{"__ignoreMap":60},[29,184,186],{"id":185},"deployment-security","Deployment Security",[14,188,189],{},"Secure your deployment pipeline to prevent unauthorized releases.",[52,191,194],{"className":192,"code":193,"language":57},[55],"# .github/workflows/deploy.yml\nname: Deploy to Fly.io\n\non:\n  push:\n    branches: [main]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Fly CLI\n        uses: superfly/flyctl-actions/setup-flyctl@master\n\n      - name: Deploy to Fly.io\n        run: flyctl deploy --remote-only\n        env:\n          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}\n",[24,195,193],{"__ignoreMap":60},[14,197,198],{},"Generate a deploy token with minimal permissions:",[52,200,203],{"className":201,"code":202,"language":57},[55],"# Create a deploy-only token\nfly tokens create deploy -x 720h\n\n# Store this token in GitHub Secrets as FLY_API_TOKEN\n",[24,204,202],{"__ignoreMap":60},[47,206,208],{"id":207},"flyio-security-checklist","Fly.io Security Checklist",[210,211,212,219,222,228,231,234,237,240,243,246],"ul",{},[213,214,215,216,218],"li",{},"All secrets stored via ",[24,217,26],{},", not in fly.toml",[213,220,221],{},"Internal services use private networking (.internal or .flycast)",[213,223,224,225],{},"HTTPS forced with ",[24,226,227],{},"force_https = true",[213,229,230],{},"Dockerfile uses non-root user",[213,232,233],{},"Health checks configured and working",[213,235,236],{},"Deploy tokens have minimal permissions",[213,238,239],{},"Fly API token stored securely in CI/CD secrets",[213,241,242],{},"Auto-stop enabled for cost and security (idle machines)",[213,244,245],{},"Volumes encrypted at rest (Fly.io default)",[213,247,248],{},"IP allowlisting for admin endpoints",[29,250,252],{"id":251},"scaling-security-considerations","Scaling Security Considerations",[14,254,255],{},"When scaling across regions, each instance needs the same security configuration:",[52,257,260],{"className":258,"code":259,"language":57},[55],"# Scale to multiple regions securely\nfly scale count 2 --region sea,iad\n\n# Each region gets the same secrets automatically\n# But verify they're set correctly\nfly secrets list\n",[24,261,259],{"__ignoreMap":60},[29,263,265],{"id":264},"logging-and-audit","Logging and Audit",[14,267,268],{},"Fly.io provides logging that you should configure for security monitoring:",[52,270,273],{"className":271,"code":272,"language":57},[55],"# View live logs\nfly logs\n\n# Ship logs to external service (recommended for production)\nfly logs --instance all | your-log-shipper\n\n# Or use the Fly log shipping integration\nfly secrets set LOGTAIL_TOKEN=\"your-token\"\n",[24,274,272],{"__ignoreMap":60},[276,277,278,285,297,303],"faq-section",{},[279,280,282],"faq-item",{"question":281},"Can I use .env files with Fly.io?",[14,283,284],{},"You should not commit .env files or include them in your Docker image. Use\nfly secrets set\nto inject environment variables at runtime. For local development, use a .env file but add it to .gitignore.",[279,286,288],{"question":287},"How do I connect to a Fly Postgres database securely?",[14,289,290,291,296],{},"Fly Postgres runs on the private network by default. Connect using the .internal hostname from your app:\npostgres://user:",[292,293,295],"a",{"href":294},"mailto:pass@your-db.internal","pass@your-db.internal",":5432/db\n. Never expose Postgres publicly unless you have a specific need and proper firewall rules.",[279,298,300],{"question":299},"Are Fly.io volumes encrypted?",[14,301,302],{},"Yes, Fly.io volumes are encrypted at rest by default. However, you should still implement application-level encryption for highly sensitive data and ensure your app handles data securely in memory.",[279,304,306],{"question":305},"How do I restrict which users can deploy?",[14,307,308],{},"Use Fly.io organizations and teams to manage access. Create deploy tokens with limited permissions for CI/CD, and use personal tokens only for development. Audit token usage in the Fly.io dashboard.",[29,310,312],{"id":311},"what-checkyourvibe-detects","What CheckYourVibe Detects",[14,314,315],{},"When you scan your Fly.io project with CheckYourVibe, we automatically detect:",[210,317,318,321,324,327,330,333],{},[213,319,320],{},"Secrets hardcoded in fly.toml or Dockerfiles",[213,322,323],{},"Missing HTTPS enforcement",[213,325,326],{},"Services exposed publicly that should be internal",[213,328,329],{},"Dockerfiles running as root",[213,331,332],{},"Missing health check configurations",[213,334,335],{},"Overly permissive network configurations",[14,337,338,339,342],{},"Run ",[24,340,341],{},"npx checkyourvibe scan"," before deploying to catch these issues automatically.",[344,345,346,352,357],"related-articles",{},[347,348],"related-card",{"description":349,"href":350,"title":351},"Secure your Convex backend when vibe coding. Learn argument validation, authentication patterns, authorization rules, an","/blog/guides/convex","Convex Security Guide for Vibe Coders",[347,353],{"description":354,"href":355,"title":356},"Security guide for GitHub Copilot users. Learn to review suggestions, protect secrets with .copilotignore, and build sec","/blog/guides/copilot","GitHub Copilot Security Guide: Safe AI-Assisted Coding",[347,358],{"description":359,"href":360,"title":361},"Built an app with Cursor? Here's what to check for security. Common vulnerabilities in Cursor-generated code and how to ","/blog/guides/cursor-security-guide","Cursor Security: What Every Vibe Coder Needs to Know",{"title":60,"searchDepth":363,"depth":363,"links":364},2,[365,366,372,375,378,381,382,385,386,387],{"id":31,"depth":363,"text":32},{"id":41,"depth":363,"text":42,"children":367},[368,370,371],{"id":49,"depth":369,"text":50},3,{"id":66,"depth":369,"text":67},{"id":84,"depth":369,"text":85},{"id":94,"depth":363,"text":95,"children":373},[374],{"id":107,"depth":369,"text":108},{"id":117,"depth":363,"text":118,"children":376},[377],{"id":130,"depth":369,"text":131},{"id":143,"depth":363,"text":144,"children":379},[380],{"id":156,"depth":369,"text":157},{"id":166,"depth":363,"text":167},{"id":185,"depth":363,"text":186,"children":383},[384],{"id":207,"depth":369,"text":208},{"id":251,"depth":363,"text":252},{"id":264,"depth":363,"text":265},{"id":311,"depth":363,"text":312},"guides","2026-01-22","Learn how to secure your Fly.io deployments when vibe coding. Cover secrets management, private networking, machine security, and deployment best practices.",false,"md",[394,396,398],{"question":281,"answer":395},"You should not commit .env files or include them in your Docker image. Use fly secrets set to inject environment variables at runtime.",{"question":287,"answer":397},"Fly Postgres runs on the private network by default. Connect using the .internal hostname from your app.",{"question":299,"answer":399},"Yes, Fly.io volumes are encrypted at rest by default.","blue",null,"Fly.io security, vibe coding Fly.io, Fly secrets, Fly private networking, edge deployment security",{},true,"Secure your Fly.io edge deployments with proper secrets management, private networking, and machine hardening.","/blog/guides/fly-io","[object Object]","TechArticle",{"title":5,"description":390},{"loc":406},"blog/guides/fly-io",[],"summary_large_image","tVFmnKiYCPYWGOcuWYYIu_LVhqFjGrGXAUdCf5SLGKY",1775843930083]