Guides
Deploy your first server
The full path from an SSH key to a server you can log in to, with how to poll the deployment and what to read when one fails.
A script that deploys without anyone watching needs more than the Quickstart shows: an SSH key stored in advance, a first-boot configuration, a wait for the deployment to finish, and a way to read the operation when it fails. In the samples, <api-key> stands for your API key, and a name in braces such as {serviceId} for an id an earlier step returned.
1. Store an SSH key
A key stored in the organization can be put on any server you deploy. Add SSH key takes a name and, as publicKey, the whole line of your .pub file (~/.ssh/id_ed25519.pub, for example). RSA, ED25519 and ECDSA keys are accepted.
curl --request POST \
--url https://api.serverside.com/v1/organization/ssh-keys \
--header 'Authorization: Bearer <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "laptop",
"publicKey": "<public-key>"
}
'The call answers 201 with the stored key. Keep its id; List SSH keys finds it again later.
2. Get the server
Pick a plan and a datacenter with stock, then order and pay for the server as in the Quickstart, which ends with its id. An organization with hourly billing, shown as billing.isUsageBased true on Get entitlements, creates the service in one call instead, with Create bare metal service. plan takes the plan's id or its slug, and datacenter a datacenter code such as ams1 or the datacenter's id. Generate the idempotency key once (the output of uuidgen will do) and send the same value on a retry, so a request repeated after a network error is recognised as the same one:
curl --request POST \
--url https://api.serverside.com/v1/baremetal \
--header 'Authorization: Bearer <api-key>' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'Content-Type: application/json' \
--data '
{
"plan": "<plan-id>",
"datacenter": "<datacenter-code>",
"name": "web-1"
}
'The call answers 201 with the new service, and its data.id is the {serviceId} the deploy, status and operation requests take. A retry with the same key answers 200 with the server the first request created and places nothing new; the same key sent with a different body answers 409 IDEMPOTENCY_KEY_REUSED. An organization without hourly billing gets 422 USAGE_BILLING_DISABLED.
The server gets an IPv4 /30 and an IPv6 /120 unless the request asks otherwise (Billing covers the sizes), and the deployment in step 3 needs the IPv4 address. bootMode and ipxeScript in the same body start the server from your own iPXE script instead of an image, as Custom iPXE scripts describes.
A plan that sold out between listing and ordering is refused with 422 and the code OFFERING_NOT_AVAILABLE, by this call and by an order alike. If another order takes the last server in the moment after that check, this call answers 409 with PROVISIONING_FAILED; list the plans again before retrying. A term order holds no server while its invoice is unpaid, so a plan that sells out before the payment leaves the paid order FAILED for support to provision or refund. A declined card never shows up at this stage. It appears in the result of Pay invoice, when the invoice is paid.
3. Deploy with a first-boot configuration
cloudInit takes a cloud-config document, run by the image's cloud-init on first boot. This one installs two packages and creates a user:
#cloud-config
packages:
- htop
- fail2ban
users:
- name: deploy
groups: sudo
shell: /bin/bash
The deployment request carries it as a single JSON string, each line break written as \n. image takes an id or a slug from List OS images; a slug such as ubuntu-24.04 picks the first active image that carries it.
curl --request POST \
--url https://api.serverside.com/v1/baremetal/{serviceId}/deploy \
--header 'Authorization: Bearer <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"image": "<image-slug>",
"sshKeyIds": [
"<ssh-key-id>"
],
"raidVariant": "RAID1",
"cloudInit": "#cloud-config\npackages:\n - htop\n - fail2ban\nusers:\n - name: deploy\n groups: sudo\n shell: /bin/bash"
}
'The call answers 201 with the operation it started, of type DEPLOYMENT; keep its id for step 5.
cloudInit is for Linux images. A Windows deployment accepts the field and never reads it, and a deployment that installs an application refuses it. On a Linux image without the cloud-init package the deployment fails in its last step, after the disk has been written.
The request has no hostname field. The installed system takes its hostname from the server's name: a valid hostname or fully qualified domain name is used in lower case, any other name is turned into a single label (lower case, every other character replaced by -, at most 63 characters), and a name with nothing usable left becomes server- followed by the first eight hex characters of the service id. A rename takes effect on the next deployment.
The API checks the request before anything is written, and a request that breaks one of these rules is refused with 422 VALIDATION_FAILED and a detail naming it:
Detail code | Rule |
|---|---|
LOGIN_CREDENTIAL_REQUIRED | The server needs a way in. A Linux deployment takes a password, an SSH key or a cloudInit document; a Windows deployment takes a password. |
DEPLOY_SELECTION_INVALID | The request names both image and application, or neither. |
OPERATING_SYSTEM_NOT_FOUND, APPLICATION_NOT_FOUND | No image has that id or slug, or no application has that id or slug. |
OPERATING_SYSTEM_UNAVAILABLE | The image has been retired, or every image with that slug has. |
APPLICATION_UNAVAILABLE | The application has been retired, or no current image can run it. |
APPLICATION_METADATA_UNSUPPORTED, APPLICATION_METADATA_INVALID | applicationMetadata was sent without an application or for one that takes none, or does not match the application's metadataSchema. |
RAID_VARIANT_UNSUPPORTED | The image does not offer that raidVariant. RAID is for Linux images only. |
RAID_DRIVE_COUNT_UNSUPPORTED | The server has too few drives for that raidVariant (two for RAID0 and RAID1, three for RAID5, four for RAID6 and RAID10), or an odd number for RAID10. |
CLOUD_INIT_UNSUPPORTED | cloudInit was sent with an application. |
Two other mistakes answer with different statuses. An applicationMetadata that is not a JSON object answers 400 VALIDATION_ERROR with an INVALID_VALUE detail, and an sshKeyIds entry that is not one of the organization's keys answers 404 ENTITY_NOT_FOUND. A server with no primary IPv4 address is refused with 422 PRIMARY_IPV4_REQUIRED before any of the checks in the table. While another operation, a network change or a BMC reset is under way, the request answers 409 with OPERATION_IN_PROGRESS, NETWORK_CHANGE_IN_PROGRESS or BMC_RESET_IN_PROGRESS.
4. Wait for it to finish
While an operation runs, the service's state is OPERATION_IN_PROGRESS. Send Get service status every 20 seconds or so until it is not:
curl --request GET \
--url https://api.serverside.com/v1/baremetal/{serviceId}/status \
--header 'Authorization: Bearer <api-key>'Between polls, data.operation.lastProgressMessage names the stage the deployment has reached. Once the state changes, data.lastOperation.state says how it ended: COMPLETED means the server is installed. Its address is primaryIpv4 on Get bare metal service, and operatingSystem.username there is the user to log in as: the username the deploy request named, or the image's defaultUser when it named none. Windows images always create Administrator, with the request's password, and take no SSH keys.
5. When a deployment fails
A FAILED operation keeps its trail. Get operation with the operation's id returns failureCategory, the lastProgressMessage and updates: every progress report the deployment sent, each with its time (at), progress and message. details repeats what the deployment ran with: the operatingSystem or application, the raidVariant and the username.
curl --request GET \
--url https://api.serverside.com/v1/baremetal/{serviceId}/operations/{operationId} \
--header 'Authorization: Bearer <api-key>'The platform sets these categories itself:
failureCategory | What happened |
|---|---|
CHECK_IN_TIMED_OUT | The server never checked in after its network boot, so the install did not start. |
NODE_PREPARE_FAILED | The server could not be prepared for the install. |
BMC_POWER_COMMAND_FAILED | The management controller could not power-cycle the server. |
CHECK_IN_PROFILE_BUILD_FAILED | The server checked in, but its install instructions could not be built. |
CHECK_IN_INTERFACE_MISSING | The server checked in without a port that one of its networks is attached to. |
OPERATION_TIMED_OUT | The deployment did not finish within its time limit. |
An application whose install fails after the operating system stage reports SCRIPT_DOWNLOAD_FAILED, SCRIPT_CHECKSUM_MISMATCH or INSTALL_SCRIPT_FAILED: its installer could not be downloaded, did not match its recorded hash, or exited with an error. The operating system stays installed and reachable in that case, and /var/log/cloud-init-output.log on the server holds the installer's output. A failure inside the operating system install reports failureCategory as UNKNOWN for now. The lastProgressMessage and the last updates are then what separate a disk or RAID step from an image download that did not finish.
A new deployment can start as soon as the service is ACTIVE again. If the second attempt fails at the same stage, contact support through the form in the cloud console or at tech@serverside.com. The form does not attach the service, so name the server in the message and include the operation's id, its failureCategory and its lastProgressMessage.