brainstorming
OBobra/superpowers298k2,631 new stars in the last 7 days
End-to-end playbook for moving a Laravel or static site from a Forge/DigitalOcean server to Laravel Cloud, from preflight to decommissioning the old droplet. Use when asked to "move a site to Laravel Cloud", "migrate from Forge to Cloud", "cut over to Cloud", "decommission the old server", "upgrade an old Laravel app so it can run on Cloud", or when planning or briefing such a move. For day-to-day Cloud commands see the laravel-cloud-ops skill; for Openprovider DNS changes see the openprovider-dns skill.
A phased checklist distilled from moving ~35 sites from Forge/DigitalOcean to Laravel Cloud. Work through the phases in order. Do not skip preflight.
Related skills: laravel-cloud-ops (Cloud CLI/API recipes), openprovider-dns (zone edits), flare (errors), agent-browser (screenshots, back office tests).
Before accessing provider accounts, read spatie-ops for private authentication, the shared API helper and Forge/DO identity checks.
op read "op://<vault>/<item>/credential"), are used inline and never written to disk.dig +short NS example.com. Only continue if the zone is in an account we control (for example DigitalOcean or Openprovider). If the client or a third party hosts DNS (Microsoft 365, a registrar we have no login for, another agency), stop: create nothing on Cloud and report back./home/forge/*, Forge API /servers/{id}/sites)crontab -l, /etc/crontab)du -sh storage/app persistent/), files written at runtimecomposer.lock)utf8mb4 defaults in config/database.php. Do not carry over an old utf8 / utf8_unicode_ci config.$2a$, or other non-standard bcrypt) fail verification. Set HASH_VERIFY=false and test a real login.{{ $x or 'default' }} syntax (use ??). Also check published vendor views (resources/views/vendor/mail, notifications).AWS_* env names for your own config; they collide with the managed queue's SQS settings.spatie/laravel-flare). Register it in bootstrap/app.php: ->withExceptions(fn (Exceptions $exceptions) => Flare::handles($exceptions)).between() are ignored) and converts them to UTC at deploy time. Use plain UTC windows with a margin.production environment, branch and deploy commands (php artisan migrate --force etc.).suspend_seconds: 120, backups on (7 days), public endpoint off (only on temporarily for imports).PATCH /environments/{id} with filesystem_keys, see laravel-cloud-ops):
league/flysystem-aws-s3-v3throw to false. Restore throw => true where failed writes matter, and re-add any root, visibility, url or options in a service providerpublic/ assets: Cloud's edge caches public/ files for 2 hours with no purge per file, so versioned assets from a bucket avoid stale CSS/JS.s-maxage (300 for client sites). Cloud ignores stale-while-revalidate; expired entries go straight to origin.APP_URL, project icon.wildcard_enabled: false, www_redirect: www_to_root, verification_method: real_time. Add the pre-verification records Cloud shows (_cf-custom-hostname TXT and _acme-challenge CNAME, also for www) so SSL is ready before the DNS switch.utf8 silently turns emoji into ?):MYSQL_PWD="$DB_PASSWORD" mysqldump --single-transaction --quick --no-tablespaces \
--set-gtid-purged=OFF --skip-lock-tables --default-character-set=utf8mb4 \
-u "$DB_USERNAME" "$DB_DATABASE" | gzip > example-$(date +%Y%m%d-%H%M%S).sql.gz
gzcat example-*.sql.gz | tail -1 # must say "Dump completed"
Strip DEFINER= clauses if the dump has views/routines:
gzcat dump.sql.gz | sed -E 's/DEFINER=`[^`]+`@`[^`]+`//g' | gzip > dump-nodefiner.sql.gz
php artisan migrate --force on Cloud, close the endpoint.# bucket endpoint + read_write key come from the Cloud API (never print them)
export RCLONE_CONFIG_R2_TYPE=s3 RCLONE_CONFIG_R2_PROVIDER=Cloudflare RCLONE_CONFIG_R2_NO_CHECK_BUCKET=true \
RCLONE_CONFIG_R2_ENDPOINT="$EP" RCLONE_CONFIG_R2_ACCESS_KEY_ID="$AK" RCLONE_CONFIG_R2_SECRET_ACCESS_KEY="$SK"
rclone sync /home/forge/example.com/storage/app/public r2:<bucket> --transfers 8 --stats-one-line
diff), files and bytes (find -type f | wc -l plus summed sizes vs rclone size --json).Test against the Cloud origin, not DNS: curl --resolve example.com:443:<cloud-ip> https://example.com/ (the IP is in the domain's DNS records in Cloud).
cloud command:run (tinker). Log in, edit, save. Delete the temporary admin afterwards and confirm./up uptime check, intervals) as described in laravel-cloud-ops.php artisan down (or an nginx maintenance response for non-Laravel). Stop its cron and daemons.location / {
proxy_pass https://<cloud-ip>;
proxy_http_version 1.1;
proxy_set_header Host example.com;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_ssl_server_name on;
proxy_ssl_name example.com;
}
sudo nginx -t && sudo service nginx reload, then curl --resolve example.com:443:<old-ip> https://example.com/ must return the Cloud site.
www, read the zone back. Leave MX/TXT untouched.dig +short @<ns> example.com A). Some providers serve stale answers for hours; keep the proxy running until all agree.failed or stays pending after DNS is correct, delete and re-add it (same options as above) and re-add the verification records.staging., localhost.). Remove them first: a released IP that DNS still points to enables subdomain takeover. Same check before releasing a reserved IP.DELETE /servers/{id} with {"preserve_at_provider": false}), then confirm at DigitalOcean that the droplet returns 404. Very old droplets may not be deletable through Forge; the owner deletes those in the DO panel.OBobra/superpowers298k2,631 new stars in the last 7 days
OBobra/superpowers298k2,631 new stars in the last 7 days
OBobra/superpowers298k2,631 new stars in the last 7 days
OBobra/superpowers298k2,631 new stars in the last 7 days
OBobra/superpowers298k2,631 new stars in the last 7 days
MAmattpocock/skills288k10,178 new stars in the last 7 days