Documentation
New here? Start with this page. BasicDeploy is the simplest way to take an app you (or your AI assistant) built and put it live on the internet — with a database, file storage and a public web address already set up. Below: what it is in plain English, then the how-tos, the API, the MCP connector, hosted auth for your own apps, and pricing. No DevOps background needed.
What is BasicDeploy? (in plain English)
BasicDeploy runs your apps for you. You send us your code — by uploading a project, or by letting your AI assistant deploy it over a connection called MCP — and we hand back a live app at its own HTTPS web address. No servers to rent, no Dockerfiles to write, no DNS to configure, no cloud console to learn. If you've ever wished you could just say "run this and give me a link," that's the whole idea.
What every app gets
Every app you create comes wired up with a private PostgreSQL database, S3-compatible file storage, a public HTTPS URL, and — when you want it — SSH access and a browser terminal. You don't configure any of it: your code just reads the DATABASE_URL and S3 settings we inject, and it works. Everything is included in the flat price; there are no per-gigabyte or per-request bills.
Who it's for
Solo builders and small teams who want to ship without becoming DevOps engineers, and AI agents that need somewhere to deploy the apps they build. Simple side projects, prototypes, internal tools and small production apps all fit comfortably.
Two ways to use it
1) From your AI assistant — add BasicDeploy as a connector (Claude, ChatGPT, Gemini, Cursor and more) and it can create and deploy apps for you right in the chat. 2) From the dashboard — upload your project, watch it go live, open a terminal, and manage databases, domains and billing from the website. Most people mix both.
Quickstart
- Sign in with your email — we send a magic link, so there is no password to manage.
- Click Create Container. You get a running container with PostgreSQL, S3-compatible storage and a public URL.
- Open the built-in terminal to write code, or upload a project from the Deploy page.
Core concepts
A container is the unit you work with on BasicDeploy. Each one bundles your app, a PostgreSQL database, S3-compatible object storage and a public HTTPS URL, with every credential your app needs already in its environment. The database and storage bucket are assigned per user and shared across the containers you own.
What's in a container
Your app runs inside the container and listens on port 8080. BasicDeploy's proxy maps the container's subdomain — https://your-subdomain.basicdeploy.com — to that port, so anything you serve on 0.0.0.0:8080 is live on the public URL.
Auto-sleep vs. always-on
Pro and Scale containers are always on and never sleep. Free containers sleep when idle and wake automatically on the next request, so the first hit after sleeping may take a moment; a $4/mo add-on keeps a Free container always on. Note: waking restarts the container but not your app process, so register a start command in an executable /workspace/.bd_boot.sh and BasicDeploy runs it on every wake (for example put `cd /workspace && python3 app.py` in that file and `chmod +x` it). Without it the container wakes but your app stays down.
Plans & limits
Every plan includes PostgreSQL, object storage and a public URL on each container. Higher tiers raise the container count, memory and storage, keep your containers always on, and allow custom domains.
| Plan | Price | Containers | Memory / container | Storage | Always-on | Custom domains |
|---|---|---|---|---|---|---|
| Free | $0 | 3 | 256 MB | 1 GB | $4/mo add-on | — |
| Pro | $12/mo | 3 | 1 GB | 5 GB | Included · opt-in | 3 |
| Scale | $39/mo | 10 | 2 GB | 10 GB | Included · opt-in | 10 |
| Enterprise | Custom | Custom | Custom | Custom | Included · opt-in | Custom |
Need a Free-plan container to stay up? Add an always-on add-on for $4/mo per container. Pro and Scale are always on.
Deploying an app
Deploy from the Deploy page in the dashboard, or POST a project archive to the API. BasicDeploy unpacks the tarball into /workspace, detects the runtime from the files it contains, installs dependencies and starts your app in the background. You can also deploy from CI with the BasicDeploy GitHub Action (github.com/marketplace/actions/basicdeploy): add BASICDEPLOY_API_KEY as a repository secret and use AkaciaNL/basicdeploy-action@v1 to deploy on push, or run create, wake, sleep, logs and delete as steps.
Runtime detection
The runtime is chosen from the files in your project root, in this order: a Dockerfile builds from your own image; otherwise package.json runs as Node (npm install && npm start), requirements.txt as Python (pip install -r requirements.txt && python app.py), and go.mod as Go (go build && ./main).
Bind to 0.0.0.0:8080
Your app must listen on 0.0.0.0:8080 — that is the port the public URL is wired to. Binding to localhost or another port means the proxy can't reach it, and the URL shows a 'nothing listening on 8080' notice.
Python — requirements.txt + app.py
# requirements.txt
flask
# app.py
from flask import Flask
app = Flask(__name__)
@app.get("/")
def home():
return "Hello from BasicDeploy"
# Must bind 0.0.0.0:8080 — the public URL is wired to this port.
app.run(host="0.0.0.0", port=8080)Node — package.json + npm start
// package.json
{ "name": "app", "scripts": { "start": "node server.js" } }
// server.js
const http = require("http")
http
.createServer((req, res) => res.end("Hello from BasicDeploy"))
.listen(8080, "0.0.0.0") // bind 0.0.0.0:8080Deploy logs
Startup output is captured to /workspace/deploy.log and errors to /workspace/deploy-error.log. Both are surfaced through the logs endpoint and the dashboard, so you can watch install and boot output without opening a shell.
Deploy from CI (GitHub Actions)
Deploy on every push with the BasicDeploy GitHub Action, available on the GitHub Marketplace (github.com/marketplace/actions/basicdeploy). It packages your repository, deploys it, and can also manage containers as workflow steps.
Add your API key as a secret
In your GitHub repository open Settings, then Secrets and variables, then Actions, and add a secret named BASICDEPLOY_API_KEY with a key from your API keys page. The action reads it at run time and never stores it.
Workflow (.github/workflows/deploy.yml)
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: AkaciaNL/basicdeploy-action@v1
with:
api-key: ${{ secrets.BASICDEPLOY_API_KEY }}Manage containers from CI
Set the command input to create, wake, sleep, logs, get, list or delete to run those as steps (delete needs confirm: true). Omit container-id on deploy to create a new container, or pass one to deploy into the same container on every push.
Web terminal & exec
Every container has a built-in terminal in the dashboard for an interactive shell in /app (your deployed files are under /workspace). To run one-off commands from a script or agent, POST to the exec endpoint and read the command's output from the response.
curl -X POST https://basicdeploy.com/api/containers/{id}/exec \
-H "Authorization: Bearer bd_your_api_key" \
-H "Content-Type: application/json" \
-d '{"command":"ls -la /workspace"}'Database (PostgreSQL)
Each container starts with DATABASE_URL already set, pointing at your PostgreSQL database. Connect with any Postgres client or driver — no credentials to configure. The database is also reachable from outside a container, at ssh.basicdeploy.com:5432 with sslmode=disable, using the credentials on the Connect tab.
Shell (psql)
# Shell (psql) — DATABASE_URL is already in the environment.
psql "$DATABASE_URL" -c "SELECT version();"Node.js
// Node.js — node-postgres (npm i pg)
import { Pool } from 'pg'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const { rows } = await pool.query('SELECT NOW()')
console.log(rows[0])Python
# Python — psycopg (pip install "psycopg[binary]")
import os, psycopg
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
print(conn.execute("SELECT version()").fetchone())From your machine (SSH tunnel)
# 1) Open the tunnel (leave it running): local :15432 -> the database.
ssh -N -L 15432:shared-db:5432 [email protected] -p 2222 -i basicdeploy_key
# 2) In another terminal, connect to localhost:15432 with your DATABASE_URL creds:
psql "postgresql://<user>:<password>@localhost:15432/<database>"
# ...or point TablePlus / DBeaver / DataGrip at host localhost, port 15432.The database is provisioned per user and shared across all of your containers, so they can read and write the same data. Use separate schemas or table prefixes if you want to keep apps isolated.
Object storage (S3)
Every container is wired to an S3-compatible bucket (MinIO) through the S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY and S3_BUCKET variables. Use any S3 client — just enable path-style addressing, which MinIO requires. The bucket is also reachable from outside a container, at http://ssh.basicdeploy.com:9000 (path-style), using the keys on the Connect tab.
MinIO client (mc)
mc alias set bd "$S3_ENDPOINT" "$S3_ACCESS_KEY" "$S3_SECRET_KEY"
mc cp ./photo.jpg "bd/$S3_BUCKET/photo.jpg"Python — boto3
import os, boto3
from botocore.config import Config
s3 = boto3.client(
"s3",
endpoint_url=os.environ["S3_ENDPOINT"],
aws_access_key_id=os.environ["S3_ACCESS_KEY"],
aws_secret_access_key=os.environ["S3_SECRET_KEY"],
# MinIO needs path-style addressing.
config=Config(s3={"addressing_style": "path"}),
)
s3.upload_file("photo.jpg", os.environ["S3_BUCKET"], "photo.jpg")Node.js
// Node.js — AWS SDK v3 (npm i @aws-sdk/client-s3)
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { readFileSync } from 'node:fs'
const s3 = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: 'us-east-1',
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
},
forcePathStyle: true, // MinIO
})
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: 'photo.jpg',
Body: readFileSync('photo.jpg'),
}))From your machine (SSH tunnel)
# 1) Open the tunnel (leave it running): local :19000 -> MinIO.
ssh -N -L 19000:minio:9000 [email protected] -p 2222 -i basicdeploy_key
# 2) Point any S3 client at the local endpoint (path-style), with your S3_* keys:
mc alias set bd http://localhost:19000 "<S3_ACCESS_KEY>" "<S3_SECRET_KEY>"
mc ls "bd/<S3_BUCKET>"The bucket is assigned per user and shared by your containers, the same way the database is.
Kafka (streaming & queues)
Every account gets a Kafka broker on the shared cluster (SASL/SCRAM, SCRAM-SHA-256), shared by all your containers. Inside a container, KAFKA_BOOTSTRAP, KAFKA_USERNAME, KAFKA_PASSWORD and KAFKA_GROUP_PREFIX are preset. From outside, use the external bootstrap (ssh.basicdeploy.com:9094) with the same SASL credentials.
You produce and consume; topics are created, purged and deleted for you via the Connect tab, the REST API (/api/kafka/*), or the MCP tools (get_kafka, create_topic, purge_topic, delete_topic). Auto-create is off — create a topic before producing to it.
Produce (Python)
# In your container the env is preset. From outside, use the EXTERNAL bootstrap.
# Python (confluent-kafka):
from confluent_kafka import Producer
p = Producer({
"bootstrap.servers": os.environ["KAFKA_BOOTSTRAP"],
"security.protocol": "SASL_PLAINTEXT",
"sasl.mechanism": "SCRAM-SHA-256",
"sasl.username": os.environ["KAFKA_USERNAME"],
"sasl.password": os.environ["KAFKA_PASSWORD"],
})
# Create the topic first (Connect tab / POST /api/kafka/topics) — auto-create is off.
p.produce(os.environ["KAFKA_GROUP_PREFIX"] + "orders", b"hello")
p.flush()
# Consumers: your group.id MUST start with KAFKA_GROUP_PREFIX (e.g. u-<hex>.myworker).Isolation: you only ever see your own topics. Your topic names and consumer-group ids live under your prefix (u-<hex>.) — a group id that doesn't start with it is rejected.
Environment variables
Every container is started with its credentials already in the environment, so your code can connect without any configuration:
DATABASE_URLS3_ENDPOINTS3_ACCESS_KEYS3_SECRET_KEYS3_BUCKET
Authentication
The browser app uses a session from your magic link. For scripts and AI agents, create an API key under API Keys and send it as a Bearer token. Keys are shown once and can be revoked at any time.
curl -H "Authorization: Bearer bd_your_api_key" \
https://basicdeploy.com/api/containersFull keys vs. scoped guest keys
A key you create under API Keys is a full-account key: it can manage every container you own. When you share a container, BasicDeploy also issues a container-scoped guest key so an agent can work on just that one container without touching the rest of your account.
- ✓A scoped guest key can read its container, stream logs, run commands and upload files — for that one container only.
- ✕It cannot create or delete containers, deploy, manage API keys or billing, or reach any other container — everything else returns 403.
REST API
All endpoints are under https://basicdeploy.com and accept either a session token or an API key.
| Method | Endpoint | What it does |
|---|---|---|
| POST | /api/containers | Create a container with a database, storage and public URL |
| GET | /api/containers | List the containers you own or that are shared with you |
| GET | /api/containers/{id} | Fetch one container; credentials are returned to its owner only |
| DELETE | /api/containers/{id} | Permanently delete a container and everything in it |
| POST | /api/containers/{id}/exec | Run a shell command inside a container |
| GET | /api/containers/{id}/logs | Read recent container logs |
| POST | /api/containers/{id}/upload | Upload a single file into a container (streamed, up to 100 MB) |
| POST | /api/containers/{id}/share | Give another person access by email address |
| DELETE | /api/containers/{id}/share | Revoke a person's access to a container |
| POST | /api/containers/{id}/sleep | Put a container to sleep |
| POST | /api/containers/{id}/wake | Wake a sleeping container |
| POST | /api/containers/{id}/always-on | Toggle always-on for a container |
| GET | /api/containers/{id}/domains | List the custom domains on a container |
| POST | /api/containers/{id}/domains | Attach a custom domain to a container |
| POST | /api/deploy | Upload a project archive and run it |
Every container ships with PostgreSQL, S3-compatible object storage and a public HTTPS URL. Through the API you can run commands, stream files in and out, read logs, deploy a whole project and share access — the same operations the web terminal and Deploy page use.
Create a container
curl -X POST https://basicdeploy.com/api/containers \
-H "Authorization: Bearer bd_your_api_key"Run a command
curl -X POST https://basicdeploy.com/api/containers/{id}/exec \
-H "Authorization: Bearer bd_your_api_key" \
-H "Content-Type: application/json" \
-d '{"command":"ls -la /workspace"}'Read recent logs
curl -H "Authorization: Bearer bd_your_api_key" \
https://basicdeploy.com/api/containers/{id}/logsUpload a file
curl -X POST https://basicdeploy.com/api/containers/{id}/upload \
-H "Authorization: Bearer bd_your_api_key" \
-F "[email protected]" -F "path=/workspace"Deploy a project archive
tar -czf app.tgz .
curl -X POST https://basicdeploy.com/api/deploy \
-H "Authorization: Bearer bd_your_api_key" \
-F "[email protected]"MCP server
BasicDeploy speaks the Model Context Protocol (MCP), so an AI assistant can drive your containers as tools — create, deploy, exec, read logs and more. Deploy without leaving the chat window.
Remote connector (recommended)
The easiest way: add BasicDeploy as a connector in Claude, ChatGPT, or Gemini. One URL, sign in once with your BasicDeploy account — no install, no API key.
https://mcp.basicdeploy.com/mcpIn your assistant's connector settings, add a custom connector with this URL and sign in when prompted. New here? You can create your BasicDeploy account right during sign-in, then come straight back.
How to add BasicDeploy to Claude
The remote connector is the easiest path — no install, no API key. In Claude (web or desktop app):
- Open Settings → Connectors in Claude.
- Click “Add custom connector.”
- Paste the connector URL shown below and confirm.
- Sign in with your BasicDeploy account when prompted — or create one right there and come back. Done: Claude can now create containers and deploy your apps straight from the chat.
https://mcp.basicdeploy.com/mcpChatGPT and Gemini work the same way — add a custom connector with the same URL in their connector settings.
Local (stdio) server
Prefer to run it yourself (Cursor, a local agent, CI)? Add the stdio server to your MCP config with an API key:
{
"mcpServers": {
"basicdeploy": {
"command": "npx",
"args": ["-y", "basicdeploy-mcp"],
"env": {
"BASICDEPLOY_API_KEY": "bd_your_api_key",
"BASICDEPLOY_URL": "https://basicdeploy.com"
}
}
}
}The server reads two environment variables: BASICDEPLOY_API_KEY (a full or scoped key) and BASICDEPLOY_URL (https://basicdeploy.com). Point any MCP-capable agent — Claude, Cursor and others — at this config and it can operate your containers directly.
Available tools
list_containers- List the containers you can access.
create_container- Create a new container with a database, storage and public URL.
get_container- Fetch one container's details.
exec_command- Run a shell command inside a container and return its output.
get_logs- Read a container's recent logs.
upload_file- Stream a file into a container.
deploy_app- Upload a project archive and run it.
share_container- Share a container with someone by email.
delete_container- Permanently delete a container.
With a container-scoped guest key the same tools are available but limited to the shared container: listing shows only that container and management verbs are blocked.
Auth for your apps
In plain terms: if the app you build needs its own users to sign up and log in, BasicDeploy can run that entire login system for you. You get hosted sign-up and sign-in pages on your own isolated tenant; your app simply sends people there and gets them back signed in. No passwords to store, no login screens to build, no security to get wrong.
BasicDeploy can host a complete authentication system for the apps you build — sign-up, login and account management over standard OpenID Connect. Enable it under Auth and BasicDeploy provisions a dedicated, fully isolated identity tenant for your app. Add apps to get OIDC client credentials, then point any OIDC library at your endpoints.
OIDC endpoints
Enabling shows your issuer plus the authorize, token, userinfo and JWKS URLs. Discovery at /.well-known/openid-configuration returns them all, so most OIDC libraries need only the issuer and your client ID.
Apps and clients
Each app you create is an OIDC client with its own client ID, and a one-time secret for confidential clients. Public clients (SPA or mobile) use PKCE and no secret. Set redirect URIs to where your app receives the login callback.
How to add login to your app (step by step)
- Open Auth in BasicDeploy and click Enable. You get your own isolated identity tenant — completely separate from every other customer's users.
- Create an app: give it a name and the redirect URL(s) where users return after login (for example https://yourapp.com/callback). You get a client ID, and for confidential apps a one-time client secret — copy it right away.
- Point any OpenID Connect library at your issuer URL. Discovery (/.well-known/openid-configuration) hands the library every endpoint automatically — you usually only need the issuer, client ID and secret.
- Send users to the hosted sign-in page. They sign up or log in there and are redirected back to your app, already signed in. Read their identity — email, name, user id — from the ID token or the userinfo endpoint.
A minimal example with a standard OIDC client (Node.js). The same shape works in any language:
// Node.js — using openid-client (any OIDC library works the same way)
import { Issuer } from 'openid-client'
// Your issuer + client come from the Auth page after you create an app.
const issuer = await Issuer.discover(
'https://auth.basicdeploy.com/realms/<your-realm>'
)
const client = new issuer.Client({
client_id: '<your-client-id>',
client_secret: '<your-client-secret>', // omit for public (SPA/mobile) clients + use PKCE
redirect_uris: ['https://yourapp.com/callback'],
response_types: ['code'],
})
// 1) Send the user to the hosted sign-in page:
const url = client.authorizationUrl({ scope: 'openid email profile' })
// 2) In your /callback route, exchange the code and read the user:
const params = client.callbackParams(req)
const tokens = await client.callback('https://yourapp.com/callback', params)
const user = await client.userinfo(tokens.access_token)
// user.email, user.name, user.sub — that's your signed-in user.Because it's plain OpenID Connect, it works with off-the-shelf libraries for JavaScript, Python, Go, Java, PHP, Ruby and more — there's no BasicDeploy SDK to learn.
Email verification is yours to handle
Important: BasicDeploy auth sends NO email. Addresses are not verified for you, and no verification or password-reset messages are delivered. If your app needs verified emails, confirm the address yourself after sign-up — send your own verification mail or gate access until you do. Because no mail is sent, the hosted “forgot password” flow cannot deliver a reset link, so drive password reset from your own app if you need it.
Every customer's users live in a separate tenant — fully isolated from BasicDeploy's own accounts and from every other customer.
Connect via SSH
SSH gives you a full interactive shell inside your container, routed through a bastion. The username is your container's subdomain, and your private key is on the container's Connect panel.
ssh [email protected] -p 2222 -i basicdeploy_keyConnect to ssh.basicdeploy.com on port 2222 — the bastion, not the app's HTTPS domain. You land in /app as the appuser account; files you deploy are unpacked under /workspace. Use the shell to edit code, install packages, inspect the database or watch a process.
If the container is asleep, any REST, MCP or SSH request wakes it automatically; the first connection may take a moment.
Shared with you? Use the guest key from the container instead:
ssh [email protected] -p 2222 -i basicdeploy_guest_keyCustom domains
Pro and Scale plans let you attach your own domain with automatic HTTPS. Add the domain to a container, then create a CNAME at your DNS provider pointing your hostname at origin.basicdeploy.com. You can point several custom domains at the same container, up to your plan's limit: add each one and they all route to that container's app. Your app receives the requested hostname in the Host header, so it can serve the same content on every domain or behave differently per domain. All domains share the container's single port (0.0.0.0:8080); there is no per-domain port. Separation is name-based, route by the Host header, not by binding a different port.
# At your DNS provider, CNAME your hostname to origin.basicdeploy.com.
# We auto-issue a free Let's Encrypt HTTPS cert for it.
app.yourdomain.com CNAME origin.basicdeploy.comBasicDeploy automatically issues and renews a free Let's Encrypt certificate for your domain. No Cloudflare and no manual setup. Once your CNAME points at origin.basicdeploy.com, the certificate is issued automatically and is ready within about 2 minutes of the first request; during that short window HTTPS may briefly show a certificate warning. Your domain shows Pending until it is issued, then Active.
Custom domains are a paid-plan feature: Pro includes 3 and Scale includes 10, counted across all your containers; the Free plan has none. Use a subdomain (e.g. app.yourdomain.com); root/apex domains are not supported because they cannot use a CNAME.
Tell multiple domains apart with the Host header (Node)
// Every domain reaches the same app on one port (0.0.0.0:8080).
// Tell them apart with the Host header, not a per-domain port.
const http = require("http")
http.createServer((req, res) => {
const host = req.headers.host // e.g. "api.example.com"
if (host.startsWith("api.otherbrand")) {
res.end("brand B for " + host)
} else {
res.end("brand A for " + host)
}
}).listen(8080, "0.0.0.0")Always-on, sleep & plans
Pro and Scale containers are always on. Free containers sleep when idle and wake automatically on the next REST, MCP, SSH or web request, with a short delay on the first hit; add a $4/mo always-on add-on per container to keep a Free container running around the clock.
Need a Free container to stay up around the clock? Add an always-on add-on at $4/mo per container. Each add-on keeps one Free container running. Pro and Scale are always on.
Upgrade or add add-ons from the Billing page. Higher plans also raise your container count, memory and storage, and unlock custom domains.
API keys
Create API keys under API Keys to authenticate scripts, the MCP server and REST calls. A key is shown once at creation — copy it then — and can be revoked at any time.
Keys you create are full-account keys that manage every container you own. Sharing a container issues a container-scoped guest key limited to that one container. Send any key as a Bearer token.
Troubleshooting
- My app's URL shows nothing, or a 'not listening' notice
- Make sure your app binds 0.0.0.0:8080, not localhost or another port. The public URL is wired to port 8080 inside the container.
- The logs are empty
- Logs come from your running app plus /workspace/deploy.log and deploy-error.log. If nothing appears, the process likely exited on startup — check deploy-error.log for install or boot errors.
- My container seems unreachable after being idle
- Pro and Scale containers are always on. On Free, a container sleeps when idle and wakes on the next request, so the first connection can take a few seconds; a $4/mo add-on keeps a Free container always on.
- SSH won't connect
- Connect to ssh.basicdeploy.com on port 2222 — the bastion, not the app's HTTPS domain — using your container's subdomain as the username and the key from the Connect tab.
- My MCP or API key gets 403s
- A container-scoped guest key only works on the container it was issued for; creating, deleting, deploying and key management are blocked. Use a full-account key for those operations.