Deployment and Hosting Guide ¶
This guide covers deploying markata-go sites to various hosting platforms, from managed services like GitHub Pages and Netlify to self-hosted solutions using nginx or Docker.
Prerequisites: Before deploying, ensure you have:
- A working markata-go site that builds successfully (
markata-go build)- Configuration set up with your site URL
- Feeds configured for RSS/Atom if you want syndication
Building for Production ¶ #
Build Command ¶ #
Build your site for production using the build command:
# Standard production build
markata-go build
# Clean build (removes output directory first)
markata-go build --clean
# Build with verbose output
markata-go build -v
# Build to a custom output directory
markata-go build -o dist
# Dry run (show what would be built without writing files)
markata-go build --dry-run
Output Directory Structure ¶ #
After building, your output directory (default: public/) contains the complete static site:
public/
├── index.html # Home page (from empty-slug feed)
├── blog/
│ ├── index.html # Blog feed page 1
│ ├── page/2/index.html # Blog feed page 2
│ ├── rss.xml # RSS feed
│ └── atom.xml # Atom feed
├── tags/
│ ├── go/
│ │ ├── index.html # Tag archive
│ │ └── rss.xml # Tag RSS feed
│ └── tutorial/
│ └── index.html
├── my-first-post/
│ └── index.html # Individual post
├── about/
│ └── index.html # Page
├── static/ # Copied assets
│ ├── css/
│ ├── js/
│ └── images/
└── sitemap.xml
Clean Builds ¶ #
For production deployments, always use clean builds to ensure no stale files remain:
markata-go build --clean
Or manually remove the output directory before building:
rm -rf public && markata-go build
Environment-Specific Builds ¶ #
Use environment variables to customize builds for different environments:
# Production
MARKATA_GO_URL=https://example.com markata-go build
# Staging
MARKATA_GO_URL=https://staging.example.com markata-go build
# Preview/development
MARKATA_GO_URL=https://preview-123.example.com markata-go build
Deploying to GitHub Pages ¶ #
GitHub Pages is a free hosting service for static sites directly from a GitHub repository.
GitHub Actions Workflow (Recommended) ¶ #
Create .github/workflows/deploy.yml:
name: Deploy to GitHub Pages
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
- name: Install markata-go
run: go install github.com/example/markata-go/cmd/markata-go@latest
- name: Build site
run: markata-go build --clean
env:
MARKATA_GO_URL: ${{ vars.SITE_URL || 'https://username.github.io/repo-name' }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Repository Settings ¶ #
- Go to your repository’s Settings > Pages
- Under Source, select GitHub Actions
- The workflow will deploy on every push to
main
Custom Domains ¶ #
To use a custom domain with GitHub Pages:
- Add a
CNAMEfile to yourstatic/directory:
example.com
-
Configure DNS at your domain registrar:
- For apex domain: Add
Arecords pointing to GitHub’s IPs - For subdomain: Add a
CNAMErecord pointing tousername.github.io
- For apex domain: Add
-
Update your config:
[markata-go]
url = "https://example.com"
Manual Deployment ¶ #
For manual deployment without GitHub Actions:
# Build the site
markata-go build --clean
# Deploy using gh-pages branch
cd public
git init
git add -A
git commit -m "Deploy"
git push -f [email protected]:username/repo.git main:gh-pages
Or use the gh-pages npm package:
npm install -g gh-pages
markata-go build --clean
gh-pages -d public
Deploying to Netlify ¶ #
Netlify provides continuous deployment, serverless functions, and edge features.
netlify.toml Configuration ¶ #
Create netlify.toml in your repository root:
[build]
command = "go install github.com/example/markata-go/cmd/markata-go@latest && markata-go build --clean"
publish = "public"
[build.environment]
GO_VERSION = "1.22"
MARKATA_GO_URL = "https://example.netlify.app"
# Production context
[context.production]
environment = { MARKATA_GO_URL = "https://example.com" }
# Deploy previews
[context.deploy-preview]
command = "go install github.com/example/markata-go/cmd/markata-go@latest && markata-go build"
[context.deploy-preview.environment]
# URL is set automatically by Netlify
# Branch deploys
[context.branch-deploy]
command = "go install github.com/example/markata-go/cmd/markata-go@latest && markata-go build"
# Headers
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.html"
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"
# Redirects
[[redirects]]
from = "/old-path/*"
to = "/new-path/:splat"
status = 301
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
force = true
# SPA fallback (if needed)
# [[redirects]]
# from = "/*"
# to = "/index.html"
# status = 200
Build Settings via UI ¶ #
Alternatively, configure through the Netlify dashboard:
- Base directory: (leave empty or set if site is in subdirectory)
- Build command:
go install github.com/example/markata-go/cmd/markata-go@latest && markata-go build --clean - Publish directory:
public - Environment variables:
GO_VERSION:1.22MARKATA_GO_URL:https://your-site.netlify.app
Deploy via CLI ¶ #
Install the Netlify CLI and deploy manually:
# Install CLI
npm install -g netlify-cli
# Login
netlify login
# Initialize (first time)
netlify init
# Deploy preview
markata-go build --clean
netlify deploy --dir=public
# Deploy to production
netlify deploy --dir=public --prod
Deploying to Vercel ¶ #
Vercel offers zero-configuration deployments with excellent performance.
vercel.json Configuration ¶ #
Create vercel.json in your repository root:
{
"buildCommand": "go install github.com/example/markata-go/cmd/markata-go@latest && markata-go build --clean",
"outputDirectory": "public",
"installCommand": "echo 'No npm dependencies'",
"framework": null,
"headers": [
{
"source": "/static/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/(.*).html",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=0, must-revalidate"
}
]
}
],
"redirects": [
{
"source": "/old-path/:path*",
"destination": "/new-path/:path*",
"permanent": true
}
],
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://api.example.com/:path*"
}
]
}
Environment Variables ¶ #
Set environment variables in the Vercel dashboard or via CLI:
# Via CLI
vercel env add MARKATA_GO_URL production
# Enter: https://example.com
vercel env add MARKATA_GO_URL preview
# Enter: (leave blank to use Vercel's preview URL)
Deploy via CLI ¶ #
# Install CLI
npm install -g vercel
# Login
vercel login
# Deploy preview
markata-go build --clean
vercel --cwd public
# Deploy to production
vercel --cwd public --prod
Using vercel.json with Go ¶ #
Since Vercel needs Go installed, use their Go runtime:
{
"build": {
"env": {
"GO_VERSION": "1.22"
}
},
"buildCommand": "curl -sSL https://go.dev/dl/go1.22.0.linux-amd64.tar.gz | tar -C /usr/local -xzf - && export PATH=$PATH:/usr/local/go/bin && go install github.com/example/markata-go/cmd/markata-go@latest && ~/go/bin/markata-go build --clean",
"outputDirectory": "public"
}
Deploying to Cloudflare Pages ¶ #
Cloudflare Pages offers fast global CDN deployment with Workers integration.
Build Configuration ¶ #
Configure via the Cloudflare dashboard:
- Framework preset: None
- Build command:
curl -sSL https://go.dev/dl/go1.22.0.linux-amd64.tar.gz | tar -xzf - && ./go/bin/go install github.com/example/markata-go/cmd/markata-go@latest && ~/go/bin/markata-go build --clean - Build output directory:
public - Root directory: (leave empty)
Environment Variables ¶ #
Set in the Cloudflare dashboard under Settings > Environment variables:
| Variable | Production | Preview |
|---|---|---|
MARKATA_GO_URL |
https://example.com |
(leave empty) |
GO_VERSION |
1.22 |
1.22 |
_headers File ¶ #
Create static/_headers for custom headers (copied to output):
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
/static/*
Cache-Control: public, max-age=31536000, immutable
/*.html
Cache-Control: public, max-age=0, must-revalidate
_redirects File ¶ #
Create static/_redirects for redirects:
/old-path/* /new-path/:splat 301
/blog/old-post /blog/new-post 301
# Proxy example
/api/* https://api.example.com/:splat 200
Wrangler CLI Deployment ¶ #
# Install wrangler
npm install -g wrangler
# Login
wrangler login
# Build and deploy
markata-go build --clean
wrangler pages deploy public --project-name=my-site
Self-Hosting ¶ #
Using nginx ¶ #
nginx configuration for serving a markata-go site:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
# Document root
root /var/www/example.com/public;
index index.html;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript
application/rss+xml application/atom+xml image/svg+xml;
# MIME types for txt/md files
location ~ \.(txt|md)$ {
default_type text/plain;
charset utf-8;
}
# Static assets caching
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML files - no caching
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# XML feeds
location ~* \.(xml|rss|atom)$ {
expires 1h;
add_header Cache-Control "public";
}
# Try exact file first (for /robots.txt), then index.html, then directory
# This supports reversed redirects where canonical files are at /slug.txt
location / {
try_files $uri $uri/index.html $uri/ =404;
}
# Custom error pages
error_page 404 /404.html;
location = /404.html {
internal;
}
}
Testing the configuration:
# Test nginx configuration
sudo nginx -t
# Reload nginx
sudo systemctl reload nginx
# Verify txt files are served with correct MIME type
curl -I https://example.com/robots.txt
# Should show: Content-Type: text/plain; charset=utf-8
# Verify HTML pages work
curl -I https://example.com/blog/
# Should show: Content-Type: text/html
Using Caddy ¶ #
Caddy provides automatic HTTPS and simpler configuration:
example.com {
root * /var/www/example.com/public
file_server
# Compression
encode gzip
# Security headers
header {
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Strict-Transport-Security "max-age=63072000"
}
# MIME types for txt/md files
@txtmd path *.txt *.md
header @txtmd Content-Type "text/plain; charset=utf-8"
# Static assets caching
@static path /static/*
header @static Cache-Control "public, max-age=31536000, immutable"
# HTML no-cache
@html path *.html
header @html Cache-Control "no-cache, no-store, must-revalidate"
# Try exact file first (for /robots.txt), then index.html, then directory
try_files {path} {path}/index.html {path}/
# Custom 404
handle_errors {
rewrite * /404.html
file_server
}
}
# Redirect www to non-www
www.example.com {
redir https://example.com{uri} permanent
}
Testing the configuration:
# Validate Caddyfile
caddy validate --config /etc/caddy/Caddyfile
# Reload Caddy
sudo systemctl reload caddy
# Verify txt files are served correctly
curl -I https://example.com/robots.txt
# Should show: Content-Type: text/plain; charset=utf-8
Docker Deployment ¶ #
Dockerfile ¶ #
Create a multi-stage Dockerfile:
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /build
# Install markata-go
RUN go install github.com/example/markata-go/cmd/markata-go@latest
# Copy source
COPY . .
# Build site
ARG MARKATA_GO_URL=https://example.com
ENV MARKATA_GO_URL=$MARKATA_GO_URL
RUN markata-go build --clean
# Production stage
FROM nginx:alpine
# Copy nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built site
COPY --from=builder /build/public /usr/share/nginx/html
# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Docker nginx.conf ¶ #
Create nginx.conf for the container:
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/rss+xml image/svg+xml;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
# MIME types for txt/md files
location ~ \.(txt|md)$ {
default_type text/plain;
charset utf-8;
}
# Static caching
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Try exact file first (for /robots.txt), then index.html, then directory
location / {
try_files $uri $uri/index.html $uri/ =404;
}
error_page 404 /404.html;
}
docker-compose.yml ¶ #
For easy deployment with Docker Compose:
version: '3.8'
services:
site:
build:
context: .
args:
MARKATA_GO_URL: https://example.com
ports:
- "80:80"
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
# Optional: Caddy reverse proxy with auto-HTTPS
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- site
volumes:
caddy_data:
caddy_config:
Build and Run ¶ #
# Build image
docker build -t my-site --build-arg MARKATA_GO_URL=https://example.com .
# Run container
docker run -d -p 80:80 --name my-site my-site
# Or with docker-compose
docker-compose up -d
CI/CD Best Practices ¶ #
Caching Dependencies ¶ #
GitHub Actions ¶ #
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true # Caches Go modules automatically
- name: Cache markata-go binary
uses: actions/cache@v4
with:
path: ~/go/bin/markata-go
key: markata-go-${{ runner.os }}-${{ hashFiles('go.sum') }}
- name: Install markata-go
run: |
if [ ! -f ~/go/bin/markata-go ]; then
go install github.com/example/markata-go/cmd/markata-go@latest
fi
GitLab CI ¶ #
variables:
GOPATH: $CI_PROJECT_DIR/.go
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .go/pkg/mod/
- .go/bin/
build:
image: golang:1.22
script:
- go install github.com/example/markata-go/cmd/markata-go@latest
- markata-go build --clean
artifacts:
paths:
- public/
Environment-Specific Builds ¶ #
Create separate workflows or jobs for different environments:
name: Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
- name: Install markata-go
run: go install github.com/example/markata-go/cmd/markata-go@latest
- name: Build (Production)
if: github.ref == 'refs/heads/main'
run: markata-go build --clean
env:
MARKATA_GO_URL: https://example.com
- name: Build (Preview)
if: github.event_name == 'pull_request'
run: markata-go build --clean
env:
MARKATA_GO_URL: https://preview-${{ github.event.number }}.example.com
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: site
path: public/
Preview Deployments ¶ #
Automatically deploy preview environments for pull requests:
Netlify (automatic) ¶ #
Netlify automatically creates deploy previews for PRs.
Vercel (automatic) ¶ #
Vercel automatically creates preview deployments for PRs.
GitHub Pages with PR Previews ¶ #
name: Preview
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
- name: Build
run: |
go install github.com/example/markata-go/cmd/markata-go@latest
markata-go build --clean
env:
MARKATA_GO_URL: https://username.github.io/repo/pr-${{ github.event.number }}
- name: Deploy Preview
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
destination_dir: pr-${{ github.event.number }}
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🚀 Preview deployed to https://username.github.io/repo/pr-${{ github.event.number }}/'
})
Build Validation ¶ #
Add validation steps before deployment:
- name: Build site
run: markata-go build --clean
- name: Validate HTML
run: |
npm install -g html-validate
html-validate "public/**/*.html" || true
- name: Check for broken links
run: |
npm install -g linkinator
linkinator public --recurse --skip "^(?!https?://example.com)" || true
- name: Validate feeds
run: |
# Validate RSS
xmllint --noout public/blog/rss.xml
# Validate Atom
xmllint --noout public/blog/atom.xml
Complete CI/CD Workflow ¶ #
A full workflow with caching, validation, and deployment:
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for git dates
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
- name: Install markata-go
run: go install github.com/example/markata-go/cmd/markata-go@latest
- name: Validate config
run: markata-go config validate
- name: Build site
run: markata-go build --clean -v
env:
MARKATA_GO_URL: ${{ github.event_name == 'push' && 'https://example.com' || '' }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public
deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Troubleshooting ¶ #
Common Issues ¶ #
Build fails with “command not found”
Ensure Go is in your PATH and markata-go is installed:
export PATH=$PATH:$(go env GOPATH)/bin
go install github.com/example/markata-go/cmd/markata-go@latest
Assets not loading
Check that your url config matches your deployment URL:
[markata-go]
url = "https://example.com" # Must match actual deployment URL
RSS/Atom feeds show wrong URLs
The base URL must be set correctly for feeds to work:
MARKATA_GO_URL=https://example.com markata-go build
404 errors on page refresh
Ensure your server is configured to serve index.html for directory requests. All hosting platforms in this guide handle this automatically.
Debugging Deployments ¶ #
Use verbose mode to debug build issues:
markata-go build --clean -v
Check the resolved configuration:
markata-go config show --sources
Validate before deploying:
markata-go config validate
Next Steps ¶ #
Congratulations on deploying your site! Here are ways to enhance it further:
Share your content:
- Syndication Guide - Automatically share posts to Mastodon, Twitter, and other platforms
Add interactivity:
- Search Guide - Add client-side search to your site
- Dynamic Content Guide - Integrate with JavaScript frameworks
Extend functionality:
- Plugin Development Guide - Create custom plugins for advanced features
See Also ¶ #
- Configuration Guide - Full configuration reference
- Feeds Guide - RSS, Atom, and JSON feed configuration
- Troubleshooting - Common issues and solutions
- Quick Reference - CLI commands and config snippets