Deploying a Deno App
Deno is a modern, secure runtime for JavaScript and TypeScript, created by the original author of Node.js to address Node’s design limitations. It features a single executable, built-in TypeScript support without configuration, a comprehensive standard library, explicit permission system for security, and zero npm configuration. Deno uses URLs for imports, supports top-level await, and provides tooling for linting, formatting, testing, and documentation generation built-in. It’s ideal for building fast, reliable, and secure server-side applications with exceptional developer experience.
This comprehensive guide walks you through deploying a Deno application to Klutch.sh, covering both automatic Nixpacks-based deployments and Docker-based deployments. You’ll learn installation steps, explore sample code, configure environment variables, build HTTP servers and APIs, and discover best practices for production deployments.
Table of Contents
- Prerequisites
- Getting Started: Create a Deno App
- Sample Code Examples
- Project Structure
- Deploying Without a Dockerfile (Nixpacks)
- Deploying With a Dockerfile
- Environment Variables & Configuration
- Building HTTP Servers & APIs
- Working with External Dependencies
- Troubleshooting
- Resources
Prerequisites
To deploy a Deno application on Klutch.sh, ensure you have:
- Deno 1.40 or higher - Install Deno
- Git - For version control
- GitHub account - Klutch.sh integrates with GitHub for continuous deployments
- Klutch.sh account - Sign up for free
Getting Started: Create a Deno App
Follow these steps to create and set up a new Deno application:
-
Install Deno (if not already installed):
Terminal window curl -fsSL https://deno.land/install.sh | sh# or on macOS with Homebrew:brew install deno -
Create a new directory for your app:
Terminal window mkdir my-deno-appcd my-deno-app -
Create a basic HTTP server (`main.ts`):
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";const port = Number(Deno.env.get("PORT")) || 3000;serve((_req) => new Response("Hello from Deno on Klutch.sh!"), { port });console.log(`Server running on port ${port}`);
-
Test locally by running your server:
Terminal window deno run --allow-net --allow-env main.tsVisit http://localhost:3000 to see your app running.
- Stop the development server when ready, press `Ctrl+C`. Verify your app runs successfully before deploying to Klutch.sh.
Sample Code Examples
HTTP Server with Routing
Here’s an example of a Deno HTTP server with basic routing:
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
const port = Number(Deno.env.get("PORT")) || 3000;
const router = async (req: Request): Promise<Response> => { const url = new URL(req.url); const method = req.method;
// Home route if (url.pathname === "/" && method === "GET") { return new Response("Welcome to Deno API!", { headers: { "Content-Type": "text/html" } }); }
// Health check route if (url.pathname === "/health" && method === "GET") { return new Response( JSON.stringify({ status: "healthy", timestamp: new Date().toISOString() }), { headers: { "Content-Type": "application/json" } } ); }
// API route - get users if (url.pathname === "/api/users" && method === "GET") { const users = [ { id: 1, name: "Alice", email: "alice@example.com" }, { id: 2, name: "Bob", email: "bob@example.com" } ];
return new Response(JSON.stringify(users), { headers: { "Content-Type": "application/json" } }); }
// API route - create user if (url.pathname === "/api/users" && method === "POST") { const body = await req.json();
if (!body.name || !body.email) { return new Response( JSON.stringify({ error: "Name and email are required" }), { status: 400, headers: { "Content-Type": "application/json" } } ); }
const newUser = { id: 3, ...body };
return new Response(JSON.stringify(newUser), { status: 201, headers: { "Content-Type": "application/json" } }); }
// 404 route return new Response("Not found", { status: 404 });};
serve(router, { port });console.log(`Server running on port ${port}`);Using External Dependencies
// Import from Deno's standard libraryimport { serve } from "https://deno.land/std@0.208.0/http/server.ts";
// Import third-party libraries from esm.sh or other CDNsimport { oakRouter } from "https://deno.land/x/oak@v12.6.1/mod.ts";
// You can also use import maps (see deno.json example below)
const port = Number(Deno.env.get("PORT")) || 3000;
serve((_req) => { return new Response("Using external dependencies with Deno!");}, { port });
console.log(`Server running on port ${port}`);Working with Environment Variables
export const getConfig = () => { const port = Number(Deno.env.get("PORT")) || 3000; const apiUrl = Deno.env.get("API_URL") || "http://localhost:3000"; const databaseUrl = Deno.env.get("DATABASE_URL"); const isDevelopment = Deno.env.get("NODE_ENV") === "development"; const isProduction = Deno.env.get("NODE_ENV") === "production";
return { port, apiUrl, databaseUrl, isDevelopment, isProduction };};Project Structure
A typical Deno project has this structure:
my-deno-app/├── deno.json├── deno.lock├── main.ts├── config.ts├── handlers/│ ├── users.ts│ └── posts.ts├── models/│ └── user.ts├── utils/│ ├── db.ts│ └── logger.ts├── tests/│ └── main_test.ts├── .gitignore└── README.mddeno.json Configuration
Create a deno.json file for managing imports and build configuration:
{ "tasks": { "dev": "deno run --allow-net --allow-env --watch main.ts", "start": "deno run --allow-net --allow-env main.ts", "test": "deno test --allow-net --allow-env" }, "imports": { "std/": "https://deno.land/std@0.208.0/", "oak": "https://deno.land/x/oak@v12.6.1/" }}Deploying Without a Dockerfile
Klutch.sh uses Nixpacks to automatically detect and build your Deno application. This is the simplest deployment option that requires no additional configuration files.
-
Test your Deno app locally to ensure it runs correctly:
Terminal window deno run --allow-net --allow-env main.ts - Push your Deno application to a GitHub repository with all source code, configuration files, and lock files included.
- Log in to your Klutch.sh dashboard.
- Create a new project and give it a name (e.g., "My Deno App").
-
Create a new app with the following configuration:
- Repository - Select your Deno GitHub repository and the branch to deploy
- Traffic Type - Select HTTP (for web applications serving HTTP traffic)
- Internal Port - Set to 3000 (the default port for Deno applications)
- Region - Choose your preferred region for deployment
- Compute - Select appropriate compute resources for your application
- Instances - Start with 1 instance for testing, scale as needed
- Environment Variables - Add any required environment variables (API keys, database URLs, etc.)
If you need to customize the start command, set this Nixpacks environment variable:
START_COMMAND: Override the default start command (e.g.,deno run --allow-net --allow-env main.ts)
- Click "Create" to deploy. Klutch.sh will automatically detect your Deno project, download dependencies, and start your application.
- Once deployed, your app will be available at a URL like `example-app.klutch.sh`. Test it by visiting the URL in your browser.
Deploying With a Dockerfile
If you prefer more control over the build and runtime environment, you can use a Dockerfile. Klutch.sh will automatically detect and use any Dockerfile in your repository’s root directory.
-
Create a `Dockerfile` in your project root:
# Build stageFROM denoland/deno:alpine-1.40.0 AS builderWORKDIR /app# Copy source codeCOPY . .# Cache deno modulesRUN deno cache --import-map deno.json main.ts# Production stageFROM denoland/deno:alpine-1.40.0WORKDIR /app# Copy cached modules and source codeCOPY --from=builder /app .# Set environment variablesENV NODE_ENV=productionENV PORT=3000# Expose portEXPOSE 3000# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \CMD deno eval "Deno.httpServer((await Deno.open('/dev/null')).rid)"# Start the applicationCMD ["deno", "run", "--allow-net", "--allow-env", "main.ts"]
-
Create a `.dockerignore` file to exclude unnecessary files from the Docker build:
node_modules.git.gitignoreREADME.md.env.env.local.vscode.idea.DS_Store
- Push your code (with Dockerfile and .dockerignore) to GitHub.
-
Follow the same deployment steps as the Nixpacks method:
- Log in to Klutch.sh
- Create a new project
- Create a new app pointing to your GitHub repository
- Set the traffic type to HTTP and internal port to 3000
- Add any required environment variables
- Click “Create”
Klutch.sh will automatically detect your Dockerfile and use it to build and deploy your application.
- Your deployed app will be available at `example-app.klutch.sh` once the build and deployment complete.
Environment Variables & Configuration
Deno applications use environment variables for configuration. Set these in the Klutch.sh dashboard during app creation or update them afterward.
Common Environment Variables
# Server configurationPORT=3000NODE_ENV=production
# API configurationAPI_URL=https://example-app.klutch.shPUBLIC_API_URL=https://example-app.klutch.sh
# DatabaseDATABASE_URL=postgresql://user:password@host:5432/deno_prod
# AuthenticationSESSION_SECRET=your_secret_session_key_hereAUTH_TOKEN=your_auth_tokenJWT_SECRET=your_jwt_secret
# Third-party servicesSTRIPE_PUBLIC_KEY=pk_live_xxxxxSTRIPE_SECRET_KEY=sk_live_xxxxxSENDGRID_API_KEY=SG.xxxxx
# LoggingLOG_LEVEL=infoAccessing Environment Variables in Deno
export const config = { port: Number(Deno.env.get("PORT")) || 3000, nodeEnv: Deno.env.get("NODE_ENV") || "development", apiUrl: Deno.env.get("API_URL") || "http://localhost:3000", databaseUrl: Deno.env.get("DATABASE_URL"), sessionSecret: Deno.env.get("SESSION_SECRET"), isDevelopment: Deno.env.get("NODE_ENV") === "development", isProduction: Deno.env.get("NODE_ENV") === "production"};
// Usage in main.tsimport { config } from "./config.ts";
console.log(`Starting server on port ${config.port} in ${config.nodeEnv} mode`);Building HTTP Servers & APIs
Simple REST API
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
interface User { id: number; name: string; email: string;}
const users: User[] = [ { id: 1, name: "Alice", email: "alice@example.com" }, { id: 2, name: "Bob", email: "bob@example.com" }];
const handleRequest = async (req: Request): Promise<Response> => { const url = new URL(req.url);
// Get all users if (url.pathname === "/api/users" && req.method === "GET") { return new Response(JSON.stringify(users), { headers: { "Content-Type": "application/json" } }); }
// Get user by ID if (url.pathname.startsWith("/api/users/") && req.method === "GET") { const id = parseInt(url.pathname.split("/")[3]); const user = users.find(u => u.id === id);
if (!user) { return new Response(JSON.stringify({ error: "User not found" }), { status: 404, headers: { "Content-Type": "application/json" } }); }
return new Response(JSON.stringify(user), { headers: { "Content-Type": "application/json" } }); }
// Create user if (url.pathname === "/api/users" && req.method === "POST") { const body = await req.json() as Omit<User, "id">;
if (!body.name || !body.email) { return new Response( JSON.stringify({ error: "Name and email are required" }), { status: 400, headers: { "Content-Type": "application/json" } } ); }
const newUser: User = { id: Math.max(...users.map(u => u.id)) + 1, ...body };
users.push(newUser);
return new Response(JSON.stringify(newUser), { status: 201, headers: { "Content-Type": "application/json" } }); }
return new Response("Not found", { status: 404 });};
const port = Number(Deno.env.get("PORT")) || 3000;serve(handleRequest, { port });console.log(`API server running on port ${port}`);Working with External Dependencies
Using deno.json for Imports
{ "imports": { "std/": "https://deno.land/std@0.208.0/", "oak": "https://deno.land/x/oak@v12.6.1/", "denodb": "https://deno.land/x/denodb@1.3.4/" }}Importing from deno.json
// Using import map aliasesimport { serve } from "std/http/server.ts";import { Application, Router } from "oak";
const app = new Application();const router = new Router();
router.get("/", (ctx) => { ctx.response.body = "Hello from Oak!";});
app.use(router.routes());
const port = Number(Deno.env.get("PORT")) || 3000;await app.listen({ port });
console.log(`Server running on port ${port}`);Troubleshooting
Build Failures
Problem - Deployment fails during the build phase
Solution:
- Verify your Deno app runs locally:
deno run --allow-net --allow-env main.ts - Ensure all remote imports are accessible
- Check for permission issues (use
--allow-net,--allow-env, etc.) - Verify the entry point file exists and is correct
- Review build logs in the Klutch.sh dashboard for specific errors
- Ensure deno.lock is committed to Git for reproducible builds
Application Won’t Start
Problem - App shows as unhealthy after deployment
Solution:
- Verify the app starts locally with all required permissions
- Check that port 3000 is configured correctly in Klutch.sh
- Verify all required environment variables are set
- Check application logs in the Klutch.sh dashboard
- Ensure the entry point file is correct (typically
main.ts) - Verify that all imports resolve correctly in the deployment environment
Permission Errors
Problem - Errors about missing permissions (net, env, read, etc.)
Solution:
-
Ensure your
START_COMMANDincludes all necessary permission flags:deno run --allow-net --allow-env --allow-read main.ts -
Add permission flags to your Dockerfile CMD if using Docker
-
Only request permissions your app actually needs
-
Test locally with the same permissions you’ll use in production
Memory Issues
Problem - App crashes with out of memory errors
Solution:
- Upgrade to a larger compute tier in Klutch.sh
- Optimize algorithms and data structures
- Monitor memory usage during development:
deno --version - Implement streaming for large responses
- Consider implementing caching strategies
Slow Startup
Problem - App takes a long time to start
Solution:
- Use the multi-stage Dockerfile to cache modules during build
- Include
deno.lockin your repository for reproducible builds - Pre-cache dependencies:
deno cache --import-map deno.json main.ts - Optimize import patterns to avoid circular dependencies
- Consider lazy-loading modules in production
Module Import Errors
Problem - “Cannot find module” or import resolution errors
Solution:
- Verify all URLs in imports are correct and accessible
- Use a
deno.jsonwith import maps for consistent versioning - Ensure
deno.lockis committed to Git - Pin specific versions in imports (avoid
*versions) - Test imports locally before deploying
Resources
- Deno Official Website
- Deno Manual & Documentation
- Deno Standard Library
- Deno Third-Party Modules
- Deno Code Examples
- Nixpacks Documentation
- Klutch.sh Dashboard
Summary
Deploying a Deno application on Klutch.sh is straightforward whether you choose Nixpacks or Docker. Deno’s built-in TypeScript support, explicit permissions system, comprehensive standard library, and modern tooling make it excellent for building fast, secure, and reliable server-side applications. Both deployment methods provide reliable, scalable hosting for your Deno apps. Start with Nixpacks for simplicity, or use Docker for complete control over your build environment and dependencies. With Deno’s modern design and Klutch.sh’s scalable infrastructure, you can deploy production-ready applications with confidence.