Deploy Astro Projects with Dokploy and Nixpacks and Enable Cache to Optimize Build Speed
I. Dokploy Configuration
Dokploy is an open-source, self-hostable deployment platform designed as a free alternative to Heroku, Vercel, and Netlify, built on top of Docker and Traefik.
1. Create a New Project and Connect GitHub Repository
2. Set Environment Variables
NIXPACKS_NODE_VERSION=22NIXPACKS_PNPM_STORE_PATH=/root/.local/share/pnpm/store/v3NIXPACKS_INSTALL_CACHE_DIRS=/app/node_modulesNIXPACKS_BUILD_CACHE_DIRS=/app/node_modules/.cache,/app/astro_cache3. Disable Cache Cleanup
- Project Service → Clean Cache: Disable
- Web Server → Daily Docker Cleanup: Disable
II. Nixpacks Build Engine
Nixpacks is an open-source build tool launched by Railway that builds source code into standard Docker images. Dokploy uses Nixpacks as the default build engine and supports specifying build configurations in nixpacks.toml or nixpacks.json files. Create a nixpacks.toml file in the project root directory and configure the relevant cache directories.
Configuration Priority (Low → High):
- Provider default logic
nixpacks.toml- Environment variables
- CLI arguments
Common Environment Variables
| Variable Name | Description |
|---|---|
NIXPACKS_INSTALL_CMD | Custom install command |
NIXPACKS_BUILD_CMD | Custom build command |
NIXPACKS_START_CMD | Custom start command |
NIXPACKS_PKGS | Install extra Nix packages |
NIXPACKS_APT_PKGS | Install extra Apt packages |
NIXPACKS_INSTALL_CACHE_DIRS | Install phase cache directories |
NIXPACKS_BUILD_CACHE_DIRS | Build phase cache directories |
NIXPACKS_NO_CACHE | Disable cache (not recommended) |
NIXPACKS_CONFIG_FILE | Specify configuration file |
NIXPACKS_DEBIAN | Enable Debian base image |
III. Astro Project Configuration
Astro is a modern Web framework for content websites, especially suitable for static websites like blogs, marketing pages, and e-commerce. When a website has a large number of images and static resources, build speed may be affected. By enabling caching mechanisms, build efficiency can be significantly improved.
1. Configure Build Cache Artifact Directory:
Astro projects need to specify a cache directory in the configuration file to reuse previous build artifacts in subsequent builds. Files in this directory will be used in subsequent builds to speed up build time. This value can be an absolute path or a relative path.
//`astro.config.mjs`export default defineConfig({ cacheDir: "./astro_cache",});2. Nixpacks Cache Configuration File
Create a nixpacks.toml file in the Astro project root directory and configure cache directories and build commands.
# Use specified Node.js and pnpm versions[phases.setup]nixPkgs = ["nodejs_22", "pnpm"]
# Install dependencies and enable pnpm cache[phases.install]cmds = ["pnpm install --frozen-lockfile"]cacheDirectories = ["/root/.local/share/pnpm/store/v3"]
# Build Astro project and cache node_modules/.cache and astro_cache[phases.build]cmds = ["pnpm run build"]cacheDirectories = [ "node_modules/.cache", "astro_cache"]
# Start command (you use NGINX to serve the dist static directory, this is just a placeholder)[start]cmd = "echo 'Build complete, please access dist directory via NGINX'"3. Optimize Docker Build Context
Add .dockerignore to the Astro project root directory:
node_modulesastro_cachedist*.log.DS_Store.vscode.env*IV. Deployment and Verification
After Dokploy automatic deployment, please check the build logs for the following content to confirm that caching is effective:
1. Build Command Used Mounted Cache
RUN --mount=type=cache,id=xxxx-node_modules/cache,target=/app/node_modules/.cache \ --mount=type=cache,id=xxxx-astro_cache,target=/app/astro_cache \ pnpm run build2. Astro Build Reused Cache Entries (Especially Image Optimization)
▶ /_astro/202409272055577_Z2smeTW.avif (reused cache entry)▶ /_astro/202409272055575_Z2wPyJN.avif (reused cache entry)▶ /_astro/202409272055577_1IgP6g.avif (reused cache entry)✅ If you see the above mount cache logs and “reused cache entry” text, it means the caching mechanism has been successfully enabled and the build has achieved incremental speedup.
🎉 From the Deployments tab of the Dokploy project, you can also see that without cache settings, building the project takes 31 minutes, while with cache settings enabled, it only takes 3 minutes, greatly shortening the build time and saving traffic and bandwidth.
V. Accelerate Site with Tencent EdgeOne
Background: The “Impossible Triangle” of Cross-Border Transmission
Deploying servers in Singapore. This solution is cost-effective and does not require cumbersome filing processes, but for users in mainland China, the access experience often faces huge challenges:
- High Physical Latency: RTT (Round Trip Time) from Singapore to mainland China usually fluctuates between 50ms - 200ms.
- Severe Packet Loss: Interfered by the GFW firewall, TCP handshakes often time out, causing images to get stuck loading halfway (Pending).
- Jump Lag: Multi-page applications require long white screen waits when clicking links.
To solve this problem, we used Tencent Cloud EdgeOne in conjunction with Traefik’s refined configuration to create a “combo punch”, achieving a second-opening experience close to domestic filed websites.
Core Architecture Idea
Our optimization strategy revolves around three keywords: Anti-Packet Loss, Offload Compression, and Layered Caching.
- Protocol Layer (EdgeOne): Utilize the HTTP/3 (QUIC) protocol based on UDP characteristics to completely solve content loading interruptions caused by TCP cross-border packet loss.
- Transport Layer (Traefik -> EdgeOne): Turn off Traefik origin compression, and let EdgeOne edge nodes be responsible for more efficient Brotli intelligent compression to reduce transmission volume.
- Cache Layer (Strategy): Use
s-maxageto achieve separation of “CDN Long-term Cache” and “Browser Short-term Cache”, ensuring both CDN hit rate and content update timeliness.
I. EdgeOne Global Configuration (Enable “Anti-Packet Loss” Mode)
Although EdgeOne cannot provide domestic nodes for unfiled domains, its edge nodes (Hong Kong/Singapore) to domestic lines are optimized and support the QUIC protocol, which is the key to solving “network lag”.
Enter EdgeOne Console -> Site Acceleration -> Feature Configuration:
1. Enable HTTP/3 (QUIC) ✅
This is the most critical step. In cross-border networks with high packet loss rates, the QUIC protocol can effectively avoid TCP head-of-line blocking. After enabling, the phenomenon of images “spinning” or “loading halfway” will completely disappear.
2. Enable Intelligent Compression (Brotli + Gzip) ✅
It is recommended to enable both Brotli and Gzip. EdgeOne will prioritize returning Brotli (br) format to supported browsers, which has a compression rate 15%-20% higher than Gzip. The smaller the volume, the faster it passes through the wall.
3. Enable Cache Pre-refresh (90%) ✅
Set the pre-refresh ratio to 90%. This means that in the last 10% of the time before the cache expires, the CDN will asynchronously go back to the source to update. Users will never encounter lag caused by “cache expiration leading to source return”, achieving a 100% hit experience.
II. Traefik Configuration Optimization (Origin Strategy)
We need to modify Traefik’s dynamic_conf.yml (or Docker Labels) to do two things: Offload Compression and Inject Refined Cache Headers.
1. Disable Origin Compression
Please check your Routers configuration and remove all compress (Gzip) middlewares.
- Reason: EdgeOne is already responsible for Brotli compression. Doing Gzip at the origin again will waste CPU and may cause double compression issues.
2. Define Layered Cache Strategy (Core Code)
This is the core to solving the contradiction between “users can’t see new content after publishing articles” and “direct source return speed is too slow”.
We define a middleware specifically for HTML pages in Traefik:
http: routers: # Force HTTPS redirect idimi-uygy0r-redirect-https: entryPoints: - web rule: Host(`idimi.com`) middlewares: - idimi-uygy0r-to-https service: noop@internal priority: 1000
# Service Worker (PWA Core) - No compression, handled by EdgeOne idimi-uygy0r-sw: rule: > Host(`idimi.com`) && ( Path(`/service-worker.js`) || Path(`/sw.js`) ) service: idimi-uygy0r-app middlewares: - idimi-uygy0r-no-store - idimi-uygy0r-security-headers entryPoints: - websecure tls: certResolver: letsencrypt priority: 900
# Astro Core Static Resources (Hash Fingerprint) - No compression, handled by EdgeOne idimi-uygy0r-static-immutable: rule: > Host(`idimi.com`) && ( PathPrefix(`/_astro`) || PathPrefix(`/assets`) ) service: idimi-uygy0r-app middlewares: - idimi-uygy0r-cache-static-immutable - idimi-uygy0r-security-headers entryPoints: - websecure tls: certResolver: letsencrypt priority: 800
# Pagefind WASM Files - No compression, handled by EdgeOne idimi-uygy0r-pagefind-wasm-ctype: rule: > Host(`idimi.com`) && PathRegexp(`^/pagefind/.*\\.wasm$`) service: idimi-uygy0r-app middlewares: - idimi-uygy0r-set-wasm-ctype - idimi-uygy0r-cache-static-immutable - idimi-uygy0r-security-headers entryPoints: - websecure tls: certResolver: letsencrypt priority: 760
# Pagefind Index Files - No compression, handled by EdgeOne idimi-uygy0r-pagefind-immutable: rule: > Host(`idimi.com`) && PathPrefix(`/pagefind`) service: idimi-uygy0r-app middlewares: - idimi-uygy0r-cache-static-immutable - idimi-uygy0r-security-headers entryPoints: - websecure tls: certResolver: letsencrypt priority: 750
# Sitemap / Robots / RSS - No compression, handled by EdgeOne idimi-uygy0r-meta-short: rule: > Host(`idimi.com`) && ( Path(`/sitemap.xml`) || Path(`/robots.txt`) || Path(`/sitemap-index.xml`) || Path(`/atom.xml`) || Path(`/rss.xml`) || Path(`/feed.xml`) ) service: idimi-uygy0r-app middlewares: - idimi-uygy0r-cache-short - idimi-uygy0r-security-headers entryPoints: - websecure tls: certResolver: letsencrypt priority: 700
# Manifest - No compression, handled by EdgeOne idimi-uygy0r-manifest: rule: > Host(`idimi.com`) && ( Path(`/manifest.webmanifest`) || Path(`/site.webmanifest`) || Path(`/browserconfig.xml`) ) service: idimi-uygy0r-app middlewares: - idimi-uygy0r-cache-1d - idimi-uygy0r-security-headers entryPoints: - websecure tls: certResolver: letsencrypt priority: 650
# Other Static Files (Images/Videos etc.) - No compression, handled by EdgeOne idimi-uygy0r-public-30d: rule: > Host(`idimi.com`) && PathRegexp(`.+\\..+`) service: idimi-uygy0r-app middlewares: - idimi-uygy0r-cache-30d-swr - idimi-uygy0r-security-headers entryPoints: - websecure tls: certResolver: letsencrypt priority: 600
# HTML Pages (Fallback Rule) - No compression, handled by EdgeOne # Applied s-maxage=3600 separation strategy idimi-uygy0r-pages: rule: Host(`idimi.com`) service: idimi-uygy0r-app middlewares: - idimi-uygy0r-cache-html - idimi-uygy0r-security-headers entryPoints: - websecure tls: certResolver: letsencrypt priority: 100
services: idimi-uygy0r-app: loadBalancer: servers: - url: http://idimi-idimi-uygy0r:80 passHostHeader: true
middlewares: idimi-uygy0r-to-https: redirectScheme: scheme: https permanent: true
# --- Compression middleware definition retained but not called (removed from Routers) --- idimi-uygy0r-gzip-compress: compress: minResponseBodyBytes: 1024 excludedContentTypes: - "image/png" - "image/jpeg" - "image/gif" - "image/webp" - "image/avif" - "font/*"
idimi-uygy0r-security-headers: headers: addVaryHeader: true stsSeconds: 31536000 stsIncludeSubdomains: true stsPreload: true contentTypeNosniff: true browserXssFilter: true referrerPolicy: "strict-origin-when-cross-origin" permissionsPolicy: "geolocation=(), microphone=(), camera=()" frameDeny: true customResponseHeaders: Timing-Allow-Origin: "*"
idimi-uygy0r-no-store: headers: customResponseHeaders: Cache-Control: "no-cache, no-store, must-revalidate"
idimi-uygy0r-cache-short: headers: customResponseHeaders: Cache-Control: "public, max-age=600, stale-while-revalidate=600" Vary: "Accept-Encoding"
idimi-uygy0r-cache-1d: headers: customResponseHeaders: Cache-Control: "public, max-age=86400, stale-while-revalidate=86400" Vary: "Accept-Encoding"
idimi-uygy0r-cache-30d-swr: headers: customResponseHeaders: Cache-Control: "public, max-age=2592000, stale-while-revalidate=604800" Vary: "Accept-Encoding"
idimi-uygy0r-cache-static-immutable: headers: customResponseHeaders: Cache-Control: "public, max-age=31536000, immutable" Vary: "Accept-Encoding"
# Key Modification: HTML Cache Strategy # Browser caches for 5 minutes (300s), CDN caches for 1 hour (3600s) idimi-uygy0r-cache-html: headers: customResponseHeaders: Cache-Control: "public, max-age=300, s-maxage=3600, stale-while-revalidate=600" Vary: "Accept-Encoding"
idimi-uygy0r-set-wasm-ctype: headers: customResponseHeaders: Content-Type: "application/wasm"Configuration Explanation:
s-maxage=3600: This is a directive specifically for the CDN. EdgeOne sees this and caches the HTML page for 1 hour.max-age=300: When EdgeOne sends it to the user, it stripss-maxage, and the user’s browser only sees 300 seconds (5 minutes).- Effect: The CDN handles the traffic for 1 hour, while users only need to wait 5 minutes to see newly published articles.
III. EdgeOne Rule Engine
Due to Astro’s URL structure (no suffix) and dynamic image service (/_image), precise rules are needed to hit the cache.
In the EdgeOne Rule Engine, configure the following rules strictly in order:
Rule 1: Astro Core Static Resources (Permanent Cache)
Astro’s Assets, build artifacts, and dynamic image service, content that never changes or consumes extreme CPU, must be forcibly cached.
- Match Condition:
URL Path->Regex Match - Match Value:
^/(_astro|assets|pagefind|_image)/ - Note:
_imageis added here, specifically optimizing dynamic optimized images generated by Astro’s<Image />component. - Action:
- Node Cache: 365 days (Force)
- Browser Cache: 365 days
Rule 2: Regular Static Files
- Match Condition:
File Extensionequalspng, jpg, jpeg, webp, css, js, etc. - Action: Node Cache 30 days (Force).
Rule 3: Service Worker (PWA Core)
- Match Condition:
URL Pathequals/sw.jsor/service-worker.js. - Action: Node Cache 1 hour (Force).
- Warning: Do not cache for too long, otherwise PWA cannot be updated in time after release.
Rule 4: Fallback Rule (HTML Pages)
- Match Condition: (No condition / Match all remaining requests)
- Action:
- Node Cache: Follow Origin (i.e., read Traefik’s
s-maxage=3600). - Browser Cache: Follow Origin (i.e., read Traefik’s
max-age=300).
- Node Cache: Follow Origin (i.e., read Traefik’s
IV. Astro Code Level Optimization (Perceived Acceleration)
To make page transitions as smooth as “native apps” and completely eliminate white screen waiting during jumps, we need to utilize Astro’s Client Router (formerly View Transitions).
1. Enable Client Router
Add in the <head> of src/layouts/MainLayout.astro:
import { ClientRouter } from 'astro:transitions';
<head> <!-- Other meta tags --> <ClientRouter /></head>2. Enable Prefetch
Configure prefetch strategy in astro.config.mjs:
export default defineConfig({ // 'viewport': Download when link enters viewport (balance traffic and speed) // 'load': Download all links immediately after page loads (extreme speed, but consumes bandwidth) prefetch: { defaultStrategy: 'viewport', }, // ...});Published at: Jul 14, 2025 · Modified at: Dec 11, 2025