Docs
Get Started

"DigitalOcean" means two very different things, and which one you are on decides how much work this is.

App Platform is a PaaS. It already deploys from your repository, already holds your configuration as data, and already runs managed databases beside your app. Moving to Kuberns is mostly translation.

A Droplet is a virtual machine you configured yourself. Your application runs because someone installed packages, wrote a systemd unit, configured nginx, and set up certbot and cron. Almost none of that is in Git. The migration is largely the work of finding that configuration and expressing it as source.

Read the section that applies to you, then follow the same steps.

From App Platform

On App Platform On Kuberns
App A service inside a project
Deployed branch An environment
Service component A server resource with a Procfile command
Worker component A background worker resource
Buildpack or Dockerfile detection The AI agent, which detects framework, runtime, port, and build commands
App-level and component-level variables Environment variables, scoped per environment
Managed PostgreSQL, MySQL, MongoDB Managed datastores
Managed Redis or Valkey A Redis queue or cache resource
Custom domains Custom domains with automatic SSL
Runtime logs The environment's logs

Your app spec holds most of what you need in one place and is the fastest thing to read before you start.

Two App Platform component types have no direct equivalent. Static site components are served as files, so decide whether they belong on a container at all. Job components that run on deploy or on a schedule need a new home, either as an in-application scheduler inside a worker or as a step in your deployment.

From a Droplet

There is no mapping table for a Droplet, because a Droplet has no declared model to map. Instead, the job is to answer one question: what makes this application run, and where is that written down?

Go through the server and collect:

  • The process manager. systemctl list-units --type=service will show what is running. The ExecStart line of your unit file is your start command.
  • The reverse proxy. Your nginx or Caddy config holds the port your application actually listens on, plus any routing, rewrites, or headers the application depends on.
  • Cron. crontab -l, and /etc/cron.d/. Scheduled work is the single most-forgotten item in a Droplet migration.
  • Environment configuration. A .env file on disk, variables in the systemd unit, or values exported in a shell profile.
  • Files on disk. User uploads, generated assets, anything under a data directory or a mounted block-storage volume.
  • The database. Running on the same Droplet, or on a DigitalOcean Managed Database.
  • TLS. Certbot renewals stop mattering after migration, since Kuberns provisions certificates, but note which hostnames are covered.
  • Firewall rules, and any service that reaches the Droplet on a non-standard port.

If your Droplet runs Docker Compose, most of this is already declared and you are much closer to done than the list suggests.

What you move yourself

  • Your data. Whether it is on the Droplet or in a Managed Database, the dump and load is your step. See Step 5.
  • Files on disk or on a block-storage volume. The Kuberns documentation does not describe a persistent-volume resource, so plan for these to move into object storage. If you already use Spaces, nothing changes; your existing credentials carry over as environment variables.
  • Scheduled work. Cron entries and App Platform jobs both need a new home.
  • Anything configured only on the server. If it is not in the repository, it does not migrate on its own.

Before you start

Beyond the inventory above, record your custom domains and their current TTLs, and take a fresh database dump.

# From a Managed Database, or from Postgres on the Droplet itself
pg_dump --no-owner --no-acl -Fc \
  -h <host> -U <user> -d <database> -f do-dump.dump

Do not destroy the Droplet or the App Platform app yet. It is your rollback until Step 8 is verified.

Step 1: Connect the repository

Create the service from your Git provider and select the branch you want to deploy. Deploy it as a non-production environment first, on the Kuberns-provided URL. Your existing setup is unaffected.

The Kuberns onboarding step that chooses a deployment type: Connect Custom Repository beside Use Open source Templates, with Select Your Repository active in the sidebar

Coming from a Droplet, confirm first that the repository actually contains everything the application needs to build. Server-only patches and manually installed dependencies are common, and they surface here.

Step 2: Check what the agent detected

The agent reports the framework, root directory, dependency file, base image, web port, build commands, and process commands.

The Kuberns AI agent analyzing a repository: live detection log on the left, and the Setup, Analyze Repository, Configure Env, Build, and Deploy phase stepper on the right

Coming from a Droplet, check the web port against what your nginx config was proxying to, and the start command against your systemd ExecStart line. Those two files are the authority, not the detection. See agent failures if the result is wrong.

Step 3: Map your processes

Every process that used to run on the Droplet needs a resource on the environment:

  • The application server becomes the server resource.
  • Each worker or queue consumer becomes a background worker resource with its own Procfile command.
  • Anything that fits none of those categories can use the miscellaneous resource type.

A process with no resource simply will not run, and nothing reports an error when it does not.

Step 4: Recreate variables and provision resources

Add your environment variables to the environment, matching key names exactly, including case.

Coming from App Platform, collect both app-level and component-level variables; encrypted values will need to be supplied again from your own records rather than read back.

Coming from a Droplet, the values are wherever the application read them from: a .env file, the systemd unit, or a shell profile. Check all three.

The environment's Environment Variables tab listing DATABASE_URL, REDIS_URL, CELERY_BROKER_URL, and CELERY_RESULT_BACKEND, with every value masked

In both cases, connection strings for a database you are replacing must point at the new Kuberns resource, not the old one. Create the database, cache, and queue resources from the environment's Resources tab.

The environment's Resources tab listing a SERVER, a BACKGROUND WORKER running a celery-start command, a POSTGRES database, and a redis queue, each with its plan, memory, and storage

Connection details for a database, its name, username, password, and hostname, live on its datastore overview page.

You can also keep an existing external database and simply point the environment variables at it, provided the new environment can reach it. That is often the smallest first step for a Managed Database.

Environment variables apply on the next deploy, and saving them triggers one automatically.

Step 5: Move your data

Kuberns gives you an empty managed database and credentials to reach it. Loading the dump is your step.

# PostgreSQL
pg_restore --no-owner --no-acl \
  -h <kuberns-hostname> -U <username> -d <database> \
  do-dump.dump

Use mysql or mongorestore for the other supported database types.

Kuberns does not restore a backup into a running datastore for you, in either direction. Its own backups are dumps you download and load the same way. Test that path once now, while nothing depends on it.

Move files off the Droplet's disk or block-storage volume in the same pass. Anything the application will still write to after migration needs to be in object storage before cutover, not after.

Run the import twice: once now against the test environment, and once again as the final sync during cutover.

Step 6: Validate before cutting over

Work through the test environment on its Kuberns URL with production-shaped data.

  • The application boots and serves requests.
  • Background workers pick up jobs, and every cron entry you found now has a working replacement.
  • Migrations have run.
  • Uploads and file reads work against object storage rather than local disk.
  • Behaviour your nginx config used to provide, redirects, headers, request size limits, still holds.
  • Outbound integrations and webhooks reach the new environment.

The environment's Logs tab streaming logs for the web process, each line showing a timestamp, level, source, and message

The nginx line is worth taking seriously. Reverse-proxy configuration accumulates fixes over years, and none of it is in your repository.

Step 7: Lower your DNS TTL

Do this a day ahead. Drop the TTL on the records you are about to change to something short, such as 300 seconds, and wait for the old TTL to expire. Your rollback speed on cutover day is set entirely by the TTL you chose the day before.

Step 8: Move the domain

Add the hostname under the environment's Custom Domains, then point DNS at the target Kuberns shows you.

Subdomain   CNAME  api    -> <Kuberns hostname shown in the dashboard>
Root domain A      @      -> <server IP shown in the dashboard>

The Domains tab before a custom domain is added: the default kuberns.site URL marked Active, an empty custom-domain list, and the Add Custom Domain button

The domain moves through DNS verification, SSL provisioning, and activation. See DNS, SSL, and activation if it stalls.

Run the final data sync immediately before the switch, while the old application is not accepting writes.

Rollback

Keep the Droplet running, or the App Platform app deployed, until you are confident. With a short TTL, rolling back is pointing DNS back at the old address.

For a Droplet in particular, resist the urge to destroy it quickly. It is the only copy of configuration that was never written down anywhere else.

Common problems

The build fails although the application ran fine on the Droplet. A dependency was installed on the server by hand and is not declared in the repository.

The application is unreachable although the deploy succeeded. The port does not match. Check what nginx was proxying to.

Uploads disappear after a deploy. The application is writing to local disk. This needs object storage.

Scheduled jobs never run. Cron entries and App Platform jobs do not migrate. Each needs a replacement.

Requests fail at a size or timeout that used to work. Those limits were in your nginx configuration, not your application.