Skip to content

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:

  1. Install Deno (if not already installed):
    Terminal window
    curl -fsSL https://deno.land/install.sh | sh
    # or on macOS with Homebrew:
    brew install deno
  2. Create a new directory for your app:
    Terminal window
    mkdir my-deno-app
    cd my-deno-app
  3. 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}`);
  4. Test locally by running your server:
    Terminal window
    deno run --allow-net --allow-env main.ts

    Visit http://localhost:3000 to see your app running.

  5. 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:

main.ts
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

app.ts
// Import from Deno's standard library
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
// Import third-party libraries from esm.sh or other CDNs
import { 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

config.ts
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.md

deno.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.

  1. Test your Deno app locally to ensure it runs correctly:
    Terminal window
    deno run --allow-net --allow-env main.ts
  2. Push your Deno application to a GitHub repository with all source code, configuration files, and lock files included.
  3. Log in to your Klutch.sh dashboard.
  4. Create a new project and give it a name (e.g., "My Deno App").
  5. 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)
  6. Click "Create" to deploy. Klutch.sh will automatically detect your Deno project, download dependencies, and start your application.
  7. 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.

  1. Create a `Dockerfile` in your project root:
    # Build stage
    FROM denoland/deno:alpine-1.40.0 AS builder
    WORKDIR /app
    # Copy source code
    COPY . .
    # Cache deno modules
    RUN deno cache --import-map deno.json main.ts
    # Production stage
    FROM denoland/deno:alpine-1.40.0
    WORKDIR /app
    # Copy cached modules and source code
    COPY --from=builder /app .
    # Set environment variables
    ENV NODE_ENV=production
    ENV PORT=3000
    # Expose port
    EXPOSE 3000
    # Health check
    HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD deno eval "Deno.httpServer((await Deno.open('/dev/null')).rid)"
    # Start the application
    CMD ["deno", "run", "--allow-net", "--allow-env", "main.ts"]
  2. Create a `.dockerignore` file to exclude unnecessary files from the Docker build:
    node_modules
    .git
    .gitignore
    README.md
    .env
    .env.local
    .vscode
    .idea
    .DS_Store
  3. Push your code (with Dockerfile and .dockerignore) to GitHub.
  4. 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.

  5. 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 configuration
PORT=3000
NODE_ENV=production
# API configuration
API_URL=https://example-app.klutch.sh
PUBLIC_API_URL=https://example-app.klutch.sh
# Database
DATABASE_URL=postgresql://user:password@host:5432/deno_prod
# Authentication
SESSION_SECRET=your_secret_session_key_here
AUTH_TOKEN=your_auth_token
JWT_SECRET=your_jwt_secret
# Third-party services
STRIPE_PUBLIC_KEY=pk_live_xxxxx
STRIPE_SECRET_KEY=sk_live_xxxxx
SENDGRID_API_KEY=SG.xxxxx
# Logging
LOG_LEVEL=info

Accessing Environment Variables in Deno

config.ts
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.ts
import { config } from "./config.ts";
console.log(`Starting server on port ${config.port} in ${config.nodeEnv} mode`);

Building HTTP Servers & APIs

Simple REST API

api.ts
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 aliases
import { 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_COMMAND includes 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.lock in 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.json with import maps for consistent versioning
  • Ensure deno.lock is committed to Git
  • Pin specific versions in imports (avoid * versions)
  • Test imports locally before deploying

Resources


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.