All formats

archive/tar

LanguageGo
LicenseBSD 3-clause (Go standard library)
Repositorygithub.com/golang/go/src/archive/tar
Documentationpkg.go.dev/archive/tar
Depends onnothing (standard library)
Read / writeyes / yes; ustar, pax and GNU (reading also star and v7); sparse files on read; no compression of its own

archive/tar is a streaming TAR reader and writer in the Go standard library. tar.Reader walks the entries of any io.Reader and is itself an io.Reader for the current entry’s data. tar.Writer writes a tar.Header followed by the data. Docker, containerd, Go modules tooling and many backup tools use it.

When writing, it picks the format per header: ustar when the fields fit, pax when a name is long, a time has sub-second precision, or a number is too large (or GNU if you set Header.Format). It has no compression; wrap the file in gzip.NewReader / gzip.NewWriter.

Installation#

Nothing to install: import "archive/tar".

Basic usage: list and read a .tar.gz#

Go
package main

import (
	"archive/tar"
	"compress/gzip"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
)

func main() {
	f, err := os.Open("archive.tar.gz")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	gz, err := gzip.NewReader(f)
	if err != nil {
		log.Fatal(err)
	}
	defer gz.Close()

	tr := tar.NewReader(gz)
	for {
		hdr, err := tr.Next()
		if errors.Is(err, io.EOF) {
			break // end of archive
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%c %10d %s  %s\n", hdr.Typeflag, hdr.Size, hdr.ModTime.Format("2006-01-02"), hdr.Name)
		if hdr.Name == "README.md" {
			data, err := io.ReadAll(tr) // tr reads the current entry's data
			if err != nil {
				log.Fatal(err)
			}
			fmt.Println(string(data))
		}
	}
}

How to extract a tar archive safely#

os.Root (Go 1.25+ for Root.MkdirAll) confines all file operations to one directory, so names with .. or absolute paths and symlinks that point outside fail:

Go
package main

import (
	"archive/tar"
	"errors"
	"io"
	"io/fs"
	"os"
	"path/filepath"
)

func extract(r io.Reader, dir string) error {
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return err
	}
	root, err := os.OpenRoot(dir)
	if err != nil {
		return err
	}
	defer root.Close()

	tr := tar.NewReader(r)
	for {
		hdr, err := tr.Next()
		if errors.Is(err, io.EOF) {
			return nil
		}
		if err != nil {
			return err
		}
		name := filepath.FromSlash(hdr.Name)
		switch hdr.Typeflag {
		case tar.TypeDir:
			if err := root.MkdirAll(name, 0o755); err != nil {
				return err
			}
		case tar.TypeReg:
			if err := root.MkdirAll(filepath.Dir(name), 0o755); err != nil {
				return err
			}
			mode := fs.FileMode(hdr.Mode).Perm()
			out, err := root.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode)
			if err != nil {
				return err
			}
			_, err = io.Copy(out, tr)
			out.Close()
			if err != nil {
				return err
			}
		default:
			// skip symlinks, hard links and devices from untrusted archives
		}
	}
}

func main() {
	f, err := os.Open("archive.tar")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	if err := extract(f, "out"); err != nil {
		panic(err)
	}
}

How to create a .tar.gz from a folder#

Writer.AddFS (Go 1.22+) adds every file of an fs.FS:

Go
package main

import (
	"archive/tar"
	"compress/gzip"
	"log"
	"os"
)

func main() {
	out, err := os.Create("docs.tar.gz")
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()

	gz := gzip.NewWriter(out)
	tw := tar.NewWriter(gz)
	if err := tw.AddFS(os.DirFS("docs")); err != nil {
		log.Fatal(err)
	}
	// close in order: tar (writes end blocks), then gzip (writes the trailer)
	if err := tw.Close(); err != nil {
		log.Fatal(err)
	}
	if err := gz.Close(); err != nil {
		log.Fatal(err)
	}
}

AddFS stores paths relative to the fs.FS root and skips the root itself. It fails on device files and sockets.

How to write files from memory#

Go
var files = []struct{ Name, Body string }{
	{"readme.txt", "This archive contains text files.\n"},
	{"notes/todo.txt", "Write more examples.\n"},
}

tw := tar.NewWriter(w) // w is any io.Writer
for _, f := range files {
	hdr := &tar.Header{
		Name:    f.Name,
		Mode:    0o644,
		Size:    int64(len(f.Body)),
		ModTime: time.Now(),
	}
	if err := tw.WriteHeader(hdr); err != nil {
		return err
	}
	if _, err := tw.Write([]byte(f.Body)); err != nil {
		return err
	}
}
return tw.Close()

How to build a header from a file on disk#

tar.FileInfoHeader fills mode, size, times and link type from os.FileInfo; set Name to the path you want inside the archive:

Go
fi, err := os.Lstat(path)
if err != nil {
	return err
}
hdr, err := tar.FileInfoHeader(fi, "") // second arg: symlink target, if any
if err != nil {
	return err
}
hdr.Name = filepath.ToSlash(relPath)
hdr.Format = tar.FormatPAX // optional: force pax for every entry

Tips#